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