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 // SC SnapLines 9 // ======================================================================== 10 sc_require('views/drawing'); 11 /** 12 @mixin 13 @author Mike Ball 14 15 Add this Mixin to any View and it gives you an API to draw snap lines for 16 all the child views 17 */ 18 19 //the number of pixles that will cause a snap line (factor of 2?) 20 SC.SNAP_ZONE = 2; 21 22 SC.SNAP_LINE = { 23 shape: SC.LINE, 24 start: {x: 0, y: 0}, 25 end: {x: 0, y: 0}, 26 style: { 27 width: 0.5, 28 color: '#00c6ff' 29 //transparency: 0.2 30 } 31 }; 32 33 34 SC.SnapLines = { 35 36 hasSnapLines: YES, 37 38 39 40 /* 41 This method will setup the data structure required to draw snap lines 42 it should be called in dragStarted if using with an `SC.Drag` or on 43 `mouseDown` if using it with a move 44 45 @param {Array} ignoreViews array of views to not include 46 sets up the data structure used for the line drawing 47 */ 48 setupData: function(ignoreViews){ 49 if(!ignoreViews) ignoreViews = []; 50 this.removeLines(); //can't have any existing lines 51 this._xPositions = {}; 52 this._yPositions = {}; 53 54 var xPositions = this._xPositions, yPositions = this._yPositions, children = this.get('childViews'), 55 that = this, parentView, frame, minX, midX, maxX, minY, midY, maxY, factor = (SC.SNAP_ZONE*2); 56 57 58 // little insert utility 59 var insert = function(min, mid, max, child, positions){ 60 var origMin = min, origMid = mid, origMax = max; 61 min = Math.floor(min/factor); 62 mid = Math.floor(mid/factor); 63 max = Math.floor(max/factor); 64 if(positions[min]){ 65 positions[min].push({value: origMin, child: child}); 66 } 67 else{ 68 positions[min] = [{value: origMin, child: child}]; 69 } 70 71 if(positions[mid]){ 72 positions[mid].push({value: origMid, child: child}); 73 } 74 else{ 75 positions[mid] = [{value: origMid, child: child}]; 76 } 77 78 if(positions[max]){ 79 positions[max].push({value: origMax, child: child}); 80 } 81 else{ 82 positions[max] = [{value: origMax, child: child}]; 83 } 84 }; 85 86 parent = this; 87 children.forEach(function(child){ 88 if(ignoreViews.indexOf(child) < 0){ 89 frame = parent ? parent.convertFrameToView(child.get('frame'), null) : child.get('frame'); 90 91 minX = frame.x; 92 midX = SC.midX(frame); 93 maxX = frame.x + frame.width; 94 insert(minX, midX, maxX, child, xPositions); 95 96 97 minY = frame.y; 98 midY = SC.midY(frame); 99 maxY = frame.y + frame.height; 100 insert(minY, midY, maxY, child, yPositions); 101 } 102 }); 103 104 //add the parent 105 parent = this.get('parentView'); 106 frame = parent ? parent.convertFrameToView(this.get('frame'), null) : this.get('frame'); 107 this._globalFrame = frame; 108 minX = frame.x; 109 midX = SC.midX(frame); 110 maxX = frame.x + frame.width; 111 insert(minX, midX, maxX, this, xPositions); 112 113 114 minY = frame.y; 115 midY = SC.midY(frame); 116 maxY = frame.y + frame.height; 117 insert(minY, midY, maxY, this, yPositions); 118 119 120 }, 121 122 /** 123 This method will check the passed views position with the other child views 124 and draw any lines. It should be called in `dragUpdated` if using `SC.Drag` 125 or in `mouseMoved` if using a move. it will also return a hash of the 126 snapped coords in local and global coordinates 127 128 */ 129 drawLines: function(view, eventX, eventY, mouseDownX, mouseDownY){ 130 if(!this._drawingView){ 131 this._drawingView = this.createChildView(SC.DrawingView.design({ 132 shapes: [] 133 })); 134 this.appendChild(this._drawingView); 135 } 136 var factor = (SC.SNAP_ZONE*2), shapes = [], xline, yline, frame, parent, rMinX, rMidX, rMaxX, 137 rMinY, rMidY, rMaxY, rMinXMod, rMidXMod, rMaxXMod, rMinYMod, rMidYMod, rMaxYMod, xHit, yHit, 138 moveDirection = this._dragDirection(eventX, eventY, mouseDownX, mouseDownY), xValues, yValues, 139 that = this, xHitVals, yHitVals, ret; 140 //get the frame and all the relevant points of interest 141 parent = view.get('parentView'); 142 frame = parent ? parent.convertFrameToView(view.get('frame'), null) : view.get('frame'); 143 rMinX = SC.minX(frame); 144 rMidX = SC.midX(frame); 145 rMaxX = SC.maxX(frame); 146 rMinY = SC.minY(frame); 147 rMidY = SC.midY(frame); 148 rMaxY = SC.maxY(frame); 149 rMinXMod = Math.floor(rMinX/factor); 150 rMidXMod = Math.floor(rMidX/factor); 151 rMaxXMod = Math.floor(rMaxX/factor); 152 rMinYMod = Math.floor(rMinY/factor); 153 rMidYMod = Math.floor(rMidY/factor); 154 rMaxYMod = Math.floor(rMaxY/factor); 155 156 //array of tuples containing the mod and the value you need to add to the resulting position 157 xValues = moveDirection.UP ? [{mod: rMinXMod, val: 0}, {mod: rMidXMod, val: frame.width/2}, {mod: rMaxXMod, val: frame.width}] : [{mod: rMaxXMod, val: frame.width}, {mod: rMidXMod, val: frame.width/2}, {mod: rMinXMod, val: 0}]; 158 //compute the three possible line positions 159 xValues.forEach(function(xVal){ 160 if(that._xPositions[xVal.mod]){ 161 xHitVals = xVal; 162 xHit = that._xPositions[xVal.mod][0].value - that._globalFrame.x; 163 return; 164 } 165 }); 166 if(!SC.none(xHit)){ 167 xline = SC.copy(SC.SNAP_LINE); 168 xline.start = {x: xHit, y: 0}; 169 xline.end = {x: xHit, y: this._globalFrame.height}; 170 shapes.push(xline); 171 } 172 173 yValues = moveDirection.LEFT ? [{mod: rMinYMod, val: 0}, {mod: rMidYMod, val: frame.height/2}, {mod: rMaxYMod, val: frame.height}] : [{mod: rMaxYMod, val: frame.height}, {mod: rMidYMod, val: frame.height/2}, {mod: rMinYMod, val: 0}]; 174 //compute the three possible line positions 175 yValues.forEach(function(yVal){ 176 if(that._yPositions[yVal.mod]){ 177 yHitVals = yVal; 178 yHit = that._yPositions[yVal.mod][0].value - that._globalFrame.y; 179 return; 180 } 181 }); 182 if(!SC.none(yHit)){ 183 yline = SC.copy(SC.SNAP_LINE); 184 yline.start = {y: yHit, x: 0}; 185 yline.end = {y: yHit, x: this._globalFrame.width}; 186 shapes.push(yline); 187 } 188 this._drawingView.set('shapes', shapes); 189 ret = {pageX: xHit + this._globalFrame.x, pageY: yHit + this._globalFrame.y, frameX: xHit, frameY: yHit}; 190 if(xHitVals){ 191 ret.pageX -= xHitVals.val; 192 ret.frameX -= xHitVals.val; 193 } 194 if(yHitVals){ 195 ret.pageY -= yHitVals.val; 196 ret.frameY -= yHitVals.val; 197 } 198 return ret; 199 }, 200 201 /* 202 called to cleanup the lines... 203 This method should be called in `mouseUp` if doing a move and in 204 `dragEnded` if using a `SC.Drag`. 205 */ 206 removeLines: function() { 207 this._xPositions = null; 208 this._yPositions = null; 209 this._globalFrame = null; 210 if(this._drawingView) { 211 this.removeChild(this._drawingView); 212 this._drawingView = null; 213 } 214 }, 215 216 /* 217 takes the event x, y and mouseDown x, y and computes a direction 218 */ 219 _dragDirection: function(eventX, eventY, mouseDownX, mouseDownY){ 220 var deltaX = eventX - mouseDownX, deltaY = eventY - mouseDownY, ret = {}; 221 ret.UP = deltaX > 0 ? NO : YES; 222 ret.DOWN = deltaX > 0 ? YES : NO; 223 ret.LEFT = deltaY > 0 ? NO : YES; 224 ret.RIGHT = deltaY > 0 ? YES : NO; 225 return ret; 226 } 227 }; 228 229