1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/components/tabview/trench.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,658 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +// ********** 1.9 +// Title: trench.js 1.10 + 1.11 +// ########## 1.12 +// Class: Trench 1.13 +// 1.14 +// Class for drag-snapping regions; called "trenches" as they are long and narrow. 1.15 + 1.16 +// Constructor: Trench 1.17 +// 1.18 +// Parameters: 1.19 +// element - the DOM element for Item (GroupItem or TabItem) from which the trench is projected 1.20 +// xory - either "x" or "y": whether the trench's <position> is along the x- or y-axis. 1.21 +// In other words, if "x", the trench is vertical; if "y", the trench is horizontal. 1.22 +// type - either "border" or "guide". Border trenches mark the border of an Item. 1.23 +// Guide trenches extend out (unless they are intercepted) and act as "guides". 1.24 +// edge - which edge of the Item that this trench corresponds to. 1.25 +// Either "top", "left", "bottom", or "right". 1.26 +function Trench(element, xory, type, edge) { 1.27 + //---------- 1.28 + // Variable: id 1.29 + // (integer) The id for the Trench. Set sequentially via <Trenches.nextId> 1.30 + this.id = Trenches.nextId++; 1.31 + 1.32 + // --------- 1.33 + // Variables: Initial parameters 1.34 + // element - (DOMElement) 1.35 + // parentItem - <Item> which projects this trench; to be set with setParentItem 1.36 + // xory - (string) "x" or "y" 1.37 + // type - (string) "border" or "guide" 1.38 + // edge - (string) "top", "left", "bottom", or "right" 1.39 + this.el = element; 1.40 + this.parentItem = null; 1.41 + this.xory = xory; // either "x" or "y" 1.42 + this.type = type; // "border" or "guide" 1.43 + this.edge = edge; // "top", "left", "bottom", or "right" 1.44 + 1.45 + this.$el = iQ(this.el); 1.46 + 1.47 + //---------- 1.48 + // Variable: dom 1.49 + // (array) DOM elements for visible reflexes of the Trench 1.50 + this.dom = []; 1.51 + 1.52 + //---------- 1.53 + // Variable: showGuide 1.54 + // (boolean) Whether this trench will project a visible guide (dotted line) or not. 1.55 + this.showGuide = false; 1.56 + 1.57 + //---------- 1.58 + // Variable: active 1.59 + // (boolean) Whether this trench is currently active or not. 1.60 + // Basically every trench aside for those projected by the Item currently being dragged 1.61 + // all become active. 1.62 + this.active = false; 1.63 + this.gutter = Items.defaultGutter; 1.64 + 1.65 + //---------- 1.66 + // Variable: position 1.67 + // (integer) position is the position that we should snap to. 1.68 + this.position = 0; 1.69 + 1.70 + //---------- 1.71 + // Variables: some Ranges 1.72 + // range - (<Range>) explicit range; this is along the transverse axis 1.73 + // minRange - (<Range>) the minimum active range 1.74 + // activeRange - (<Range>) the currently active range 1.75 + this.range = new Range(0,10000); 1.76 + this.minRange = new Range(0,0); 1.77 + this.activeRange = new Range(0,10000); 1.78 +}; 1.79 + 1.80 +Trench.prototype = { 1.81 + // ---------- 1.82 + // Function: toString 1.83 + // Prints [Trench edge type (parentItem)] for debug use 1.84 + toString: function Trench_toString() { 1.85 + return "[Trench " + this.edge + " " + this.type + 1.86 + (this.parentItem ? " (" + this.parentItem + ")" : "") + 1.87 + "]"; 1.88 + }, 1.89 + 1.90 + //---------- 1.91 + // Variable: radius 1.92 + // (integer) radius is how far away we should snap from 1.93 + get radius() this.customRadius || Trenches.defaultRadius, 1.94 + 1.95 + setParentItem: function Trench_setParentItem(item) { 1.96 + if (!item.isAnItem) { 1.97 + Utils.assert(false, "parentItem must be an Item"); 1.98 + return false; 1.99 + } 1.100 + this.parentItem = item; 1.101 + return true; 1.102 + }, 1.103 + 1.104 + //---------- 1.105 + // Function: setPosition 1.106 + // set the trench's position. 1.107 + // 1.108 + // Parameters: 1.109 + // position - (integer) px center position of the trench 1.110 + // range - (<Range>) the explicit active range of the trench 1.111 + // minRange - (<Range>) the minimum range of the trench 1.112 + setPosition: function Trench_setPosition(position, range, minRange) { 1.113 + this.position = position; 1.114 + 1.115 + var page = Items.getPageBounds(true); 1.116 + 1.117 + // optionally, set the range. 1.118 + if (Utils.isRange(range)) { 1.119 + this.range = range; 1.120 + } else { 1.121 + this.range = new Range(0, (this.xory == 'x' ? page.height : page.width)); 1.122 + } 1.123 + 1.124 + // if there's a minRange, set that too. 1.125 + if (Utils.isRange(minRange)) 1.126 + this.minRange = minRange; 1.127 + 1.128 + // set the appropriate bounds as a rect. 1.129 + if (this.xory == "x") // vertical 1.130 + this.rect = new Rect(this.position - this.radius, this.range.min, 2 * this.radius, this.range.extent); 1.131 + else // horizontal 1.132 + this.rect = new Rect(this.range.min, this.position - this.radius, this.range.extent, 2 * this.radius); 1.133 + 1.134 + this.show(); // DEBUG 1.135 + }, 1.136 + 1.137 + //---------- 1.138 + // Function: setActiveRange 1.139 + // set the trench's currently active range. 1.140 + // 1.141 + // Parameters: 1.142 + // activeRange - (<Range>) 1.143 + setActiveRange: function Trench_setActiveRange(activeRange) { 1.144 + if (!Utils.isRange(activeRange)) 1.145 + return false; 1.146 + this.activeRange = activeRange; 1.147 + if (this.xory == "x") { // horizontal 1.148 + this.activeRect = new Rect(this.position - this.radius, this.activeRange.min, 2 * this.radius, this.activeRange.extent); 1.149 + this.guideRect = new Rect(this.position, this.activeRange.min, 0, this.activeRange.extent); 1.150 + } else { // vertical 1.151 + this.activeRect = new Rect(this.activeRange.min, this.position - this.radius, this.activeRange.extent, 2 * this.radius); 1.152 + this.guideRect = new Rect(this.activeRange.min, this.position, this.activeRange.extent, 0); 1.153 + } 1.154 + return true; 1.155 + }, 1.156 + 1.157 + //---------- 1.158 + // Function: setWithRect 1.159 + // Set the trench's position using the given rect. We know which side of the rect we should match 1.160 + // because we've already recorded this information in <edge>. 1.161 + // 1.162 + // Parameters: 1.163 + // rect - (<Rect>) 1.164 + setWithRect: function Trench_setWithRect(rect) { 1.165 + 1.166 + if (!Utils.isRect(rect)) 1.167 + Utils.error('argument must be Rect'); 1.168 + 1.169 + // First, calculate the range for this trench. 1.170 + // Border trenches are always only active for the length of this range. 1.171 + // Guide trenches, however, still use this value as its minRange. 1.172 + if (this.xory == "x") 1.173 + var range = new Range(rect.top - this.gutter, rect.bottom + this.gutter); 1.174 + else 1.175 + var range = new Range(rect.left - this.gutter, rect.right + this.gutter); 1.176 + 1.177 + if (this.type == "border") { 1.178 + // border trenches have a range, so set that too. 1.179 + if (this.edge == "left") 1.180 + this.setPosition(rect.left - this.gutter, range); 1.181 + else if (this.edge == "right") 1.182 + this.setPosition(rect.right + this.gutter, range); 1.183 + else if (this.edge == "top") 1.184 + this.setPosition(rect.top - this.gutter, range); 1.185 + else if (this.edge == "bottom") 1.186 + this.setPosition(rect.bottom + this.gutter, range); 1.187 + } else if (this.type == "guide") { 1.188 + // guide trenches have no range, but do have a minRange. 1.189 + if (this.edge == "left") 1.190 + this.setPosition(rect.left, false, range); 1.191 + else if (this.edge == "right") 1.192 + this.setPosition(rect.right, false, range); 1.193 + else if (this.edge == "top") 1.194 + this.setPosition(rect.top, false, range); 1.195 + else if (this.edge == "bottom") 1.196 + this.setPosition(rect.bottom, false, range); 1.197 + } 1.198 + }, 1.199 + 1.200 + //---------- 1.201 + // Function: show 1.202 + // 1.203 + // Show guide (dotted line), if <showGuide> is true. 1.204 + // 1.205 + // If <Trenches.showDebug> is true, we will draw the trench. Active portions are drawn with 0.5 1.206 + // opacity. If <active> is false, the entire trench will be 1.207 + // very translucent. 1.208 + show: function Trench_show() { // DEBUG 1.209 + if (this.active && this.showGuide) { 1.210 + if (!this.dom.guideTrench) 1.211 + this.dom.guideTrench = iQ("<div/>").addClass('guideTrench').css({id: 'guideTrench'+this.id}); 1.212 + var guideTrench = this.dom.guideTrench; 1.213 + guideTrench.css(this.guideRect); 1.214 + iQ("body").append(guideTrench); 1.215 + } else { 1.216 + if (this.dom.guideTrench) { 1.217 + this.dom.guideTrench.remove(); 1.218 + delete this.dom.guideTrench; 1.219 + } 1.220 + } 1.221 + 1.222 + if (!Trenches.showDebug) { 1.223 + this.hide(true); // true for dontHideGuides 1.224 + return; 1.225 + } 1.226 + 1.227 + if (!this.dom.visibleTrench) 1.228 + this.dom.visibleTrench = iQ("<div/>") 1.229 + .addClass('visibleTrench') 1.230 + .addClass(this.type) // border or guide 1.231 + .css({id: 'visibleTrench'+this.id}); 1.232 + var visibleTrench = this.dom.visibleTrench; 1.233 + 1.234 + if (!this.dom.activeVisibleTrench) 1.235 + this.dom.activeVisibleTrench = iQ("<div/>") 1.236 + .addClass('activeVisibleTrench') 1.237 + .addClass(this.type) // border or guide 1.238 + .css({id: 'activeVisibleTrench'+this.id}); 1.239 + var activeVisibleTrench = this.dom.activeVisibleTrench; 1.240 + 1.241 + if (this.active) 1.242 + activeVisibleTrench.addClass('activeTrench'); 1.243 + else 1.244 + activeVisibleTrench.removeClass('activeTrench'); 1.245 + 1.246 + visibleTrench.css(this.rect); 1.247 + activeVisibleTrench.css(this.activeRect || this.rect); 1.248 + iQ("body").append(visibleTrench); 1.249 + iQ("body").append(activeVisibleTrench); 1.250 + }, 1.251 + 1.252 + //---------- 1.253 + // Function: hide 1.254 + // Hide the trench. 1.255 + hide: function Trench_hide(dontHideGuides) { 1.256 + if (this.dom.visibleTrench) 1.257 + this.dom.visibleTrench.remove(); 1.258 + if (this.dom.activeVisibleTrench) 1.259 + this.dom.activeVisibleTrench.remove(); 1.260 + if (!dontHideGuides && this.dom.guideTrench) 1.261 + this.dom.guideTrench.remove(); 1.262 + }, 1.263 + 1.264 + //---------- 1.265 + // Function: rectOverlaps 1.266 + // Given a <Rect>, compute whether it overlaps with this trench. If it does, return an 1.267 + // adjusted ("snapped") <Rect>; if it does not overlap, simply return false. 1.268 + // 1.269 + // Note that simply overlapping is not all that is required to be affected by this function. 1.270 + // Trenches can only affect certain edges of rectangles... for example, a "left"-edge guide 1.271 + // trench should only affect left edges of rectangles. We don't snap right edges to left-edged 1.272 + // guide trenches. For border trenches, the logic is a bit different, so left snaps to right and 1.273 + // top snaps to bottom. 1.274 + // 1.275 + // Parameters: 1.276 + // rect - (<Rect>) the rectangle in question 1.277 + // stationaryCorner - which corner is stationary? by default, the top left. 1.278 + // "topleft", "bottomleft", "topright", "bottomright" 1.279 + // assumeConstantSize - (boolean) whether the rect's dimensions are sacred or not 1.280 + // keepProportional - (boolean) if we are allowed to change the rect's size, whether the 1.281 + // dimensions should scaled proportionally or not. 1.282 + // 1.283 + // Returns: 1.284 + // false - if rect does not overlap with this trench 1.285 + // newRect - (<Rect>) an adjusted version of rect, if it is affected by this trench 1.286 + rectOverlaps: function Trench_rectOverlaps(rect,stationaryCorner,assumeConstantSize,keepProportional) { 1.287 + var edgeToCheck; 1.288 + if (this.type == "border") { 1.289 + if (this.edge == "left") 1.290 + edgeToCheck = "right"; 1.291 + else if (this.edge == "right") 1.292 + edgeToCheck = "left"; 1.293 + else if (this.edge == "top") 1.294 + edgeToCheck = "bottom"; 1.295 + else if (this.edge == "bottom") 1.296 + edgeToCheck = "top"; 1.297 + } else { // if trench type is guide or barrier... 1.298 + edgeToCheck = this.edge; 1.299 + } 1.300 + 1.301 + rect.adjustedEdge = edgeToCheck; 1.302 + 1.303 + switch (edgeToCheck) { 1.304 + case "left": 1.305 + if (this.ruleOverlaps(rect.left, rect.yRange)) { 1.306 + if (stationaryCorner.indexOf('right') > -1) 1.307 + rect.width = rect.right - this.position; 1.308 + rect.left = this.position; 1.309 + return rect; 1.310 + } 1.311 + break; 1.312 + case "right": 1.313 + if (this.ruleOverlaps(rect.right, rect.yRange)) { 1.314 + if (assumeConstantSize) { 1.315 + rect.left = this.position - rect.width; 1.316 + } else { 1.317 + var newWidth = this.position - rect.left; 1.318 + if (keepProportional) 1.319 + rect.height = rect.height * newWidth / rect.width; 1.320 + rect.width = newWidth; 1.321 + } 1.322 + return rect; 1.323 + } 1.324 + break; 1.325 + case "top": 1.326 + if (this.ruleOverlaps(rect.top, rect.xRange)) { 1.327 + if (stationaryCorner.indexOf('bottom') > -1) 1.328 + rect.height = rect.bottom - this.position; 1.329 + rect.top = this.position; 1.330 + return rect; 1.331 + } 1.332 + break; 1.333 + case "bottom": 1.334 + if (this.ruleOverlaps(rect.bottom, rect.xRange)) { 1.335 + if (assumeConstantSize) { 1.336 + rect.top = this.position - rect.height; 1.337 + } else { 1.338 + var newHeight = this.position - rect.top; 1.339 + if (keepProportional) 1.340 + rect.width = rect.width * newHeight / rect.height; 1.341 + rect.height = newHeight; 1.342 + } 1.343 + return rect; 1.344 + } 1.345 + } 1.346 + 1.347 + return false; 1.348 + }, 1.349 + 1.350 + //---------- 1.351 + // Function: ruleOverlaps 1.352 + // Computes whether the given "rule" (a line segment, essentially), given by the position and 1.353 + // range arguments, overlaps with the current trench. Note that this function assumes that 1.354 + // the rule and the trench are in the same direction: both horizontal, or both vertical. 1.355 + // 1.356 + // Parameters: 1.357 + // position - (integer) a position in px 1.358 + // range - (<Range>) the rule's range 1.359 + ruleOverlaps: function Trench_ruleOverlaps(position, range) { 1.360 + return (this.position - this.radius < position && 1.361 + position < this.position + this.radius && 1.362 + this.activeRange.overlaps(range)); 1.363 + }, 1.364 + 1.365 + //---------- 1.366 + // Function: adjustRangeIfIntercept 1.367 + // Computes whether the given boundary (given as a position and its active range), perpendicular 1.368 + // to the trench, intercepts the trench or not. If it does, it returns an adjusted <Range> for 1.369 + // the trench. If not, it returns false. 1.370 + // 1.371 + // Parameters: 1.372 + // position - (integer) the position of the boundary 1.373 + // range - (<Range>) the target's range, on the trench's transverse axis 1.374 + adjustRangeIfIntercept: function Trench_adjustRangeIfIntercept(position, range) { 1.375 + if (this.position - this.radius > range.min && this.position + this.radius < range.max) { 1.376 + var activeRange = new Range(this.activeRange); 1.377 + 1.378 + // there are three ways this can go: 1.379 + // 1. position < minRange.min 1.380 + // 2. position > minRange.max 1.381 + // 3. position >= minRange.min && position <= minRange.max 1.382 + 1.383 + if (position < this.minRange.min) { 1.384 + activeRange.min = Math.min(this.minRange.min,position); 1.385 + } else if (position > this.minRange.max) { 1.386 + activeRange.max = Math.max(this.minRange.max,position); 1.387 + } else { 1.388 + // this should be impossible because items can't overlap and we've already checked 1.389 + // that the range intercepts. 1.390 + } 1.391 + return activeRange; 1.392 + } 1.393 + return false; 1.394 + }, 1.395 + 1.396 + //---------- 1.397 + // Function: calculateActiveRange 1.398 + // Computes and sets the <activeRange> for the trench, based on the <GroupItems> around. 1.399 + // This makes it so trenches' active ranges don't extend through other groupItems. 1.400 + calculateActiveRange: function Trench_calculateActiveRange() { 1.401 + 1.402 + // set it to the default: just the range itself. 1.403 + this.setActiveRange(this.range); 1.404 + 1.405 + // only guide-type trenches need to set a separate active range 1.406 + if (this.type != 'guide') 1.407 + return; 1.408 + 1.409 + var groupItems = GroupItems.groupItems; 1.410 + var trench = this; 1.411 + groupItems.forEach(function(groupItem) { 1.412 + if (groupItem.isDragging) // floating groupItems don't block trenches 1.413 + return; 1.414 + if (trench.el == groupItem.container) // groupItems don't block their own trenches 1.415 + return; 1.416 + var bounds = groupItem.getBounds(); 1.417 + var activeRange = new Range(); 1.418 + if (trench.xory == 'y') { // if this trench is horizontal... 1.419 + activeRange = trench.adjustRangeIfIntercept(bounds.left, bounds.yRange); 1.420 + if (activeRange) 1.421 + trench.setActiveRange(activeRange); 1.422 + activeRange = trench.adjustRangeIfIntercept(bounds.right, bounds.yRange); 1.423 + if (activeRange) 1.424 + trench.setActiveRange(activeRange); 1.425 + } else { // if this trench is vertical... 1.426 + activeRange = trench.adjustRangeIfIntercept(bounds.top, bounds.xRange); 1.427 + if (activeRange) 1.428 + trench.setActiveRange(activeRange); 1.429 + activeRange = trench.adjustRangeIfIntercept(bounds.bottom, bounds.xRange); 1.430 + if (activeRange) 1.431 + trench.setActiveRange(activeRange); 1.432 + } 1.433 + }); 1.434 + } 1.435 +}; 1.436 + 1.437 +// ########## 1.438 +// Class: Trenches 1.439 +// Singelton for managing all <Trench>es. 1.440 +var Trenches = { 1.441 + // --------- 1.442 + // Variables: 1.443 + // nextId - (integer) a counter for the next <Trench>'s <Trench.id> value. 1.444 + // showDebug - (boolean) whether to draw the <Trench>es or not. 1.445 + // defaultRadius - (integer) the default radius for new <Trench>es. 1.446 + // disabled - (boolean) whether trench-snapping is disabled or not. 1.447 + nextId: 0, 1.448 + showDebug: false, 1.449 + defaultRadius: 10, 1.450 + disabled: false, 1.451 + 1.452 + // --------- 1.453 + // Variables: snapping preferences; used to break ties in snapping. 1.454 + // preferTop - (boolean) prefer snapping to the top to the bottom 1.455 + // preferLeft - (boolean) prefer snapping to the left to the right 1.456 + preferTop: true, 1.457 + get preferLeft() { return !UI.rtl; }, 1.458 + 1.459 + trenches: [], 1.460 + 1.461 + // ---------- 1.462 + // Function: toString 1.463 + // Prints [Trenches count=count] for debug use 1.464 + toString: function Trenches_toString() { 1.465 + return "[Trenches count=" + this.trenches.length + "]"; 1.466 + }, 1.467 + 1.468 + // --------- 1.469 + // Function: getById 1.470 + // Return the specified <Trench>. 1.471 + // 1.472 + // Parameters: 1.473 + // id - (integer) 1.474 + getById: function Trenches_getById(id) { 1.475 + return this.trenches[id]; 1.476 + }, 1.477 + 1.478 + // --------- 1.479 + // Function: register 1.480 + // Register a new <Trench> and returns the resulting <Trench> ID. 1.481 + // 1.482 + // Parameters: 1.483 + // See the constructor <Trench.Trench>'s parameters. 1.484 + // 1.485 + // Returns: 1.486 + // id - (int) the new <Trench>'s ID. 1.487 + register: function Trenches_register(element, xory, type, edge) { 1.488 + var trench = new Trench(element, xory, type, edge); 1.489 + this.trenches[trench.id] = trench; 1.490 + return trench.id; 1.491 + }, 1.492 + 1.493 + // --------- 1.494 + // Function: registerWithItem 1.495 + // Register a whole set of <Trench>es using an <Item> and returns the resulting <Trench> IDs. 1.496 + // 1.497 + // Parameters: 1.498 + // item - the <Item> to project trenches 1.499 + // type - either "border" or "guide" 1.500 + // 1.501 + // Returns: 1.502 + // ids - array of the new <Trench>es' IDs. 1.503 + registerWithItem: function Trenches_registerWithItem(item, type) { 1.504 + var container = item.container; 1.505 + var ids = {}; 1.506 + ids.left = Trenches.register(container,"x",type,"left"); 1.507 + ids.right = Trenches.register(container,"x",type,"right"); 1.508 + ids.top = Trenches.register(container,"y",type,"top"); 1.509 + ids.bottom = Trenches.register(container,"y",type,"bottom"); 1.510 + 1.511 + this.getById(ids.left).setParentItem(item); 1.512 + this.getById(ids.right).setParentItem(item); 1.513 + this.getById(ids.top).setParentItem(item); 1.514 + this.getById(ids.bottom).setParentItem(item); 1.515 + 1.516 + return ids; 1.517 + }, 1.518 + 1.519 + // --------- 1.520 + // Function: unregister 1.521 + // Unregister one or more <Trench>es. 1.522 + // 1.523 + // Parameters: 1.524 + // ids - (integer) a single <Trench> ID or (array) a list of <Trench> IDs. 1.525 + unregister: function Trenches_unregister(ids) { 1.526 + if (!Array.isArray(ids)) 1.527 + ids = [ids]; 1.528 + var self = this; 1.529 + ids.forEach(function(id) { 1.530 + self.trenches[id].hide(); 1.531 + delete self.trenches[id]; 1.532 + }); 1.533 + }, 1.534 + 1.535 + // --------- 1.536 + // Function: activateOthersTrenches 1.537 + // Activate all <Trench>es other than those projected by the current element. 1.538 + // 1.539 + // Parameters: 1.540 + // element - (DOMElement) the DOM element of the Item being dragged or resized. 1.541 + activateOthersTrenches: function Trenches_activateOthersTrenches(element) { 1.542 + this.trenches.forEach(function(t) { 1.543 + if (t.el === element) 1.544 + return; 1.545 + if (t.parentItem && (t.parentItem.isAFauxItem || t.parentItem.isDragging)) 1.546 + return; 1.547 + t.active = true; 1.548 + t.calculateActiveRange(); 1.549 + t.show(); // debug 1.550 + }); 1.551 + }, 1.552 + 1.553 + // --------- 1.554 + // Function: disactivate 1.555 + // After <activateOthersTrenches>, disactivates all the <Trench>es again. 1.556 + disactivate: function Trenches_disactivate() { 1.557 + this.trenches.forEach(function(t) { 1.558 + t.active = false; 1.559 + t.showGuide = false; 1.560 + t.show(); 1.561 + }); 1.562 + }, 1.563 + 1.564 + // --------- 1.565 + // Function: hideGuides 1.566 + // Hide all guides (dotted lines) en masse. 1.567 + hideGuides: function Trenches_hideGuides() { 1.568 + this.trenches.forEach(function(t) { 1.569 + t.showGuide = false; 1.570 + t.show(); 1.571 + }); 1.572 + }, 1.573 + 1.574 + // --------- 1.575 + // Function: snap 1.576 + // Used to "snap" an object's bounds to active trenches and to the edge of the window. 1.577 + // If the meta key is down (<Key.meta>), it will not snap but will still enforce the rect 1.578 + // not leaving the safe bounds of the window. 1.579 + // 1.580 + // Parameters: 1.581 + // rect - (<Rect>) the object's current bounds 1.582 + // stationaryCorner - which corner is stationary? by default, the top left. 1.583 + // "topleft", "bottomleft", "topright", "bottomright" 1.584 + // assumeConstantSize - (boolean) whether the rect's dimensions are sacred or not 1.585 + // keepProportional - (boolean) if we are allowed to change the rect's size, whether the 1.586 + // dimensions should scaled proportionally or not. 1.587 + // 1.588 + // Returns: 1.589 + // (<Rect>) - the updated bounds, if they were updated 1.590 + // false - if the bounds were not updated 1.591 + snap: function Trenches_snap(rect,stationaryCorner,assumeConstantSize,keepProportional) { 1.592 + // hide all the guide trenches, because the correct ones will be turned on later. 1.593 + Trenches.hideGuides(); 1.594 + 1.595 + var updated = false; 1.596 + var updatedX = false; 1.597 + var updatedY = false; 1.598 + 1.599 + var snappedTrenches = {}; 1.600 + 1.601 + for (var i in this.trenches) { 1.602 + var t = this.trenches[i]; 1.603 + if (!t.active) 1.604 + continue; 1.605 + // newRect will be a new rect, or false 1.606 + var newRect = t.rectOverlaps(rect,stationaryCorner,assumeConstantSize,keepProportional); 1.607 + 1.608 + if (newRect) { // if rectOverlaps returned an updated rect... 1.609 + 1.610 + if (assumeConstantSize && updatedX && updatedY) 1.611 + break; 1.612 + if (assumeConstantSize && updatedX && (newRect.adjustedEdge == "left"||newRect.adjustedEdge == "right")) 1.613 + continue; 1.614 + if (assumeConstantSize && updatedY && (newRect.adjustedEdge == "top"||newRect.adjustedEdge == "bottom")) 1.615 + continue; 1.616 + 1.617 + rect = newRect; 1.618 + updated = true; 1.619 + 1.620 + // register this trench as the "snapped trench" for the appropriate edge. 1.621 + snappedTrenches[newRect.adjustedEdge] = t; 1.622 + 1.623 + // if updatedX, we don't need to update x any more. 1.624 + if (newRect.adjustedEdge == "left" && this.preferLeft) 1.625 + updatedX = true; 1.626 + if (newRect.adjustedEdge == "right" && !this.preferLeft) 1.627 + updatedX = true; 1.628 + 1.629 + // if updatedY, we don't need to update x any more. 1.630 + if (newRect.adjustedEdge == "top" && this.preferTop) 1.631 + updatedY = true; 1.632 + if (newRect.adjustedEdge == "bottom" && !this.preferTop) 1.633 + updatedY = true; 1.634 + 1.635 + } 1.636 + } 1.637 + 1.638 + if (updated) { 1.639 + rect.snappedTrenches = snappedTrenches; 1.640 + return rect; 1.641 + } 1.642 + return false; 1.643 + }, 1.644 + 1.645 + // --------- 1.646 + // Function: show 1.647 + // <Trench.show> all <Trench>es. 1.648 + show: function Trenches_show() { 1.649 + this.trenches.forEach(function(t) { 1.650 + t.show(); 1.651 + }); 1.652 + }, 1.653 + 1.654 + // --------- 1.655 + // Function: toggleShown 1.656 + // Toggle <Trenches.showDebug> and trigger <Trenches.show> 1.657 + toggleShown: function Trenches_toggleShown() { 1.658 + this.showDebug = !this.showDebug; 1.659 + this.show(); 1.660 + } 1.661 +};