// ========================================================================== // 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 datastructure 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 coodinates */ 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 relavent 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; } };