// ========================================================================== // Project: SproutCore
- JavaScript Application Framework // Copyright: ©2006-2011 Strobe Inc. and contributors. // Portions ©2008-2011 Apple Inc. All rights reserved. // License: Licensed under MIT license (see license.js) // ========================================================================== // ======================================================================== // SC
SnapLines // ======================================================================== sc_require('views/drawing'); /**
@mixin @author Mike Ball Add this Mixin to any View and it gives you an API to draw snap lines for all the child views
*/
//the number of pixles that will cause a snap line (factor of 2?) SC
.SNAP_ZONE = 2;
SC
.SNAP_LINE = {
shape: SC.LINE, start: {x: 0, y: 0}, end: {x: 0, y: 0}, style: { width: 0.5, color: '#00c6ff' //transparency: 0.2 }
};
SC
.SnapLines = {
hasSnapLines: YES, /* This method will setup the data structure required to draw snap lines it should be called in dragStarted if using with an `SC.Drag` or on `mouseDown` if using it with a move @param {Array} ignoreViews array of views to not include sets up the data structure used for the line drawing */ setupData: function(ignoreViews){ if(!ignoreViews) ignoreViews = []; this.removeLines(); //can't have any existing lines this._xPositions = {}; this._yPositions = {}; var xPositions = this._xPositions, yPositions = this._yPositions, children = this.get('childViews'), that = this, parentView, frame, minX, midX, maxX, minY, midY, maxY, factor = (SC.SNAP_ZONE*2); // little insert utility var insert = function(min, mid, max, child, positions){ var origMin = min, origMid = mid, origMax = max; min = Math.floor(min/factor); mid = Math.floor(mid/factor); max = Math.floor(max/factor); if(positions[min]){ positions[min].push({value: origMin, child: child}); } else{ positions[min] = [{value: origMin, child: child}]; } if(positions[mid]){ positions[mid].push({value: origMid, child: child}); } else{ positions[mid] = [{value: origMid, child: child}]; } if(positions[max]){ positions[max].push({value: origMax, child: child}); } else{ positions[max] = [{value: origMax, child: child}]; } }; parent = this; children.forEach(function(child){ if(ignoreViews.indexOf(child) < 0){ frame = parent ? parent.convertFrameToView(child.get('frame'), null) : child.get('frame'); minX = frame.x; midX = SC.midX(frame); maxX = frame.x + frame.width; insert(minX, midX, maxX, child, xPositions); minY = frame.y; midY = SC.midY(frame); maxY = frame.y + frame.height; insert(minY, midY, maxY, child, yPositions); } }); //add the parent parent = this.get('parentView'); frame = parent ? parent.convertFrameToView(this.get('frame'), null) : this.get('frame'); this._globalFrame = frame; minX = frame.x; midX = SC.midX(frame); maxX = frame.x + frame.width; insert(minX, midX, maxX, this, xPositions); minY = frame.y; midY = SC.midY(frame); maxY = frame.y + frame.height; insert(minY, midY, maxY, this, yPositions); }, /** This method will check the passed views position with the other child views and draw any lines. It should be called in `dragUpdated` if using `SC.Drag` or in `mouseMoved` if using a move. it will also return a hash of the snapped coords in local and global coordinates */ drawLines: function(view, eventX, eventY, mouseDownX, mouseDownY){ if(!this._drawingView){ this._drawingView = this.createChildView(SC.DrawingView.design({ shapes: [] })); this.appendChild(this._drawingView); } var factor = (SC.SNAP_ZONE*2), shapes = [], xline, yline, frame, parent, rMinX, rMidX, rMaxX, rMinY, rMidY, rMaxY, rMinXMod, rMidXMod, rMaxXMod, rMinYMod, rMidYMod, rMaxYMod, xHit, yHit, moveDirection = this._dragDirection(eventX, eventY, mouseDownX, mouseDownY), xValues, yValues, that = this, xHitVals, yHitVals, ret; //get the frame and all the relevant points of interest parent = view.get('parentView'); frame = parent ? parent.convertFrameToView(view.get('frame'), null) : view.get('frame'); rMinX = SC.minX(frame); rMidX = SC.midX(frame); rMaxX = SC.maxX(frame); rMinY = SC.minY(frame); rMidY = SC.midY(frame); rMaxY = SC.maxY(frame); rMinXMod = Math.floor(rMinX/factor); rMidXMod = Math.floor(rMidX/factor); rMaxXMod = Math.floor(rMaxX/factor); rMinYMod = Math.floor(rMinY/factor); rMidYMod = Math.floor(rMidY/factor); rMaxYMod = Math.floor(rMaxY/factor); //array of tuples containing the mod and the value you need to add to the resulting position 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}]; //compute the three possible line positions xValues.forEach(function(xVal){ if(that._xPositions[xVal.mod]){ xHitVals = xVal; xHit = that._xPositions[xVal.mod][0].value - that._globalFrame.x; return; } }); if(!SC.none(xHit)){ xline = SC.copy(SC.SNAP_LINE); xline.start = {x: xHit, y: 0}; xline.end = {x: xHit, y: this._globalFrame.height}; shapes.push(xline); } 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}]; //compute the three possible line positions yValues.forEach(function(yVal){ if(that._yPositions[yVal.mod]){ yHitVals = yVal; yHit = that._yPositions[yVal.mod][0].value - that._globalFrame.y; return; } }); if(!SC.none(yHit)){ yline = SC.copy(SC.SNAP_LINE); yline.start = {y: yHit, x: 0}; yline.end = {y: yHit, x: this._globalFrame.width}; shapes.push(yline); } this._drawingView.set('shapes', shapes); ret = {pageX: xHit + this._globalFrame.x, pageY: yHit + this._globalFrame.y, frameX: xHit, frameY: yHit}; if(xHitVals){ ret.pageX -= xHitVals.val; ret.frameX -= xHitVals.val; } if(yHitVals){ ret.pageY -= yHitVals.val; ret.frameY -= yHitVals.val; } return ret; }, /* called to cleanup the lines... This method should be called in `mouseUp` if doing a move and in `dragEnded` if using a `SC.Drag`. */ removeLines: function() { this._xPositions = null; this._yPositions = null; this._globalFrame = null; if(this._drawingView) { this.removeChild(this._drawingView); this._drawingView = null; } }, /* takes the event x, y and mouseDown x, y and computes a direction */ _dragDirection: function(eventX, eventY, mouseDownX, mouseDownY){ var deltaX = eventX - mouseDownX, deltaY = eventY - mouseDownY, ret = {}; ret.UP = deltaX > 0 ? NO : YES; ret.DOWN = deltaX > 0 ? YES : NO; ret.LEFT = deltaY > 0 ? NO : YES; ret.RIGHT = deltaY > 0 ? YES : NO; return ret; }
};