browser/components/tabview/trench.js

changeset 0
6474c204b198
     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 +};

mercurial