summaryrefslogtreecommitdiffstats
path: root/application/basilisk/components/customizableui/DragPositionManager.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'application/basilisk/components/customizableui/DragPositionManager.jsm')
-rw-r--r--application/basilisk/components/customizableui/DragPositionManager.jsm420
1 files changed, 0 insertions, 420 deletions
diff --git a/application/basilisk/components/customizableui/DragPositionManager.jsm b/application/basilisk/components/customizableui/DragPositionManager.jsm
deleted file mode 100644
index 1b4eb59dc..000000000
--- a/application/basilisk/components/customizableui/DragPositionManager.jsm
+++ /dev/null
@@ -1,420 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-Components.utils.import("resource:///modules/CustomizableUI.jsm");
-
-var gManagers = new WeakMap();
-
-const kPaletteId = "customization-palette";
-const kPlaceholderClass = "panel-customization-placeholder";
-
-this.EXPORTED_SYMBOLS = ["DragPositionManager"];
-
-function AreaPositionManager(aContainer) {
- // Caching the direction and bounds of the container for quick access later:
- let window = aContainer.ownerGlobal;
- this._dir = window.getComputedStyle(aContainer).direction;
- let containerRect = aContainer.getBoundingClientRect();
- this._containerInfo = {
- left: containerRect.left,
- right: containerRect.right,
- top: containerRect.top,
- width: containerRect.width
- };
- this._inPanel = aContainer.id == CustomizableUI.AREA_PANEL;
- this._horizontalDistance = null;
- this.update(aContainer);
-}
-
-AreaPositionManager.prototype = {
- _nodePositionStore: null,
- _wideCache: null,
-
- update: function(aContainer) {
- this._nodePositionStore = new WeakMap();
- this._wideCache = new Set();
- let last = null;
- let singleItemHeight;
- for (let child of aContainer.children) {
- if (child.hidden) {
- continue;
- }
- let isNodeWide = this._checkIfWide(child);
- if (isNodeWide) {
- this._wideCache.add(child.id);
- }
- let coordinates = this._lazyStoreGet(child);
- // We keep a baseline horizontal distance between non-wide nodes around
- // for use when we can't compare with previous/next nodes
- if (!this._horizontalDistance && last && !isNodeWide) {
- this._horizontalDistance = coordinates.left - last.left;
- }
- // We also keep the basic height of non-wide items for use below:
- if (!isNodeWide && !singleItemHeight) {
- singleItemHeight = coordinates.height;
- }
- last = !isNodeWide ? coordinates : null;
- }
- if (this._inPanel) {
- this._heightToWidthFactor = CustomizableUI.PANEL_COLUMN_COUNT;
- } else {
- this._heightToWidthFactor = this._containerInfo.width / singleItemHeight;
- }
- },
-
- /**
- * Find the closest node in the container given the coordinates.
- * "Closest" is defined in a somewhat strange manner: we prefer nodes
- * which are in the same row over nodes that are in a different row.
- * In order to implement this, we use a weighted cartesian distance
- * where dy is more heavily weighted by a factor corresponding to the
- * ratio between the container's width and the height of its elements.
- */
- find: function(aContainer, aX, aY, aDraggedItemId) {
- let closest = null;
- let minCartesian = Number.MAX_VALUE;
- let containerX = this._containerInfo.left;
- let containerY = this._containerInfo.top;
- for (let node of aContainer.children) {
- let coordinates = this._lazyStoreGet(node);
- let offsetX = coordinates.x - containerX;
- let offsetY = coordinates.y - containerY;
- let hDiff = offsetX - aX;
- let vDiff = offsetY - aY;
- // For wide widgets, we're always going to be further from the center
- // horizontally. Compensate:
- if (this.isWide(node)) {
- hDiff /= CustomizableUI.PANEL_COLUMN_COUNT;
- }
- // Then compensate for the height/width ratio so that we prefer items
- // which are in the same row:
- hDiff /= this._heightToWidthFactor;
-
- let cartesianDiff = hDiff * hDiff + vDiff * vDiff;
- if (cartesianDiff < minCartesian) {
- minCartesian = cartesianDiff;
- closest = node;
- }
- }
-
- // Now correct this node based on what we're dragging
- if (closest) {
- let doc = aContainer.ownerDocument;
- let draggedItem = doc.getElementById(aDraggedItemId);
- // If dragging a wide item, always pick the first item in a row:
- if (this._inPanel && draggedItem &&
- draggedItem.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
- return this._firstInRow(closest);
- }
- let targetBounds = this._lazyStoreGet(closest);
- let farSide = this._dir == "ltr" ? "right" : "left";
- let outsideX = targetBounds[farSide];
- // Check if we're closer to the next target than to this one:
- // Only move if we're not targeting a node in a different row:
- if (aY > targetBounds.top && aY < targetBounds.bottom) {
- if ((this._dir == "ltr" && aX > outsideX) ||
- (this._dir == "rtl" && aX < outsideX)) {
- return closest.nextSibling || aContainer;
- }
- }
- }
- return closest;
- },
-
- /**
- * "Insert" a "placeholder" by shifting the subsequent children out of the
- * way. We go through all the children, and shift them based on the position
- * they would have if we had inserted something before aBefore. We use CSS
- * transforms for this, which are CSS transitioned.
- */
- insertPlaceholder: function(aContainer, aBefore, aWide, aSize, aIsFromThisArea) {
- let isShifted = false;
- let shiftDown = aWide;
- for (let child of aContainer.children) {
- // Don't need to shift hidden nodes:
- if (child.getAttribute("hidden") == "true") {
- continue;
- }
- // If this is the node before which we're inserting, start shifting
- // everything that comes after. One exception is inserting at the end
- // of the menupanel, in which case we do not shift the placeholders:
- if (child == aBefore && !child.classList.contains(kPlaceholderClass)) {
- isShifted = true;
- // If the node before which we're inserting is wide, we should
- // shift everything one row down:
- if (!shiftDown && this.isWide(child)) {
- shiftDown = true;
- }
- }
- // If we're moving items before a wide node that were already there,
- // it's possible it's not necessary to shift nodes
- // including & after the wide node.
- if (this.__undoShift) {
- isShifted = false;
- }
- if (isShifted) {
- // Conversely, if we're adding something before a wide node, for
- // simplicity's sake we move everything including the wide node down:
- if (this.__moveDown) {
- shiftDown = true;
- }
- if (aIsFromThisArea && !this._lastPlaceholderInsertion) {
- child.setAttribute("notransition", "true");
- }
- // Determine the CSS transform based on the next node:
- child.style.transform = this._getNextPos(child, shiftDown, aSize);
- } else {
- // If we're not shifting this node, reset the transform
- child.style.transform = "";
- }
- }
- if (aContainer.lastChild && aIsFromThisArea &&
- !this._lastPlaceholderInsertion) {
- // Flush layout:
- aContainer.lastChild.getBoundingClientRect();
- // then remove all the [notransition]
- for (let child of aContainer.children) {
- child.removeAttribute("notransition");
- }
- }
- delete this.__moveDown;
- delete this.__undoShift;
- this._lastPlaceholderInsertion = aBefore;
- },
-
- isWide: function(aNode) {
- return this._wideCache.has(aNode.id);
- },
-
- _checkIfWide: function(aNode) {
- return this._inPanel && aNode && aNode.firstChild &&
- aNode.firstChild.classList.contains(CustomizableUI.WIDE_PANEL_CLASS);
- },
-
- /**
- * Reset all the transforms in this container, optionally without
- * transitioning them.
- * @param aContainer the container in which to reset transforms
- * @param aNoTransition if truthy, adds a notransition attribute to the node
- * while resetting the transform.
- */
- clearPlaceholders: function(aContainer, aNoTransition) {
- for (let child of aContainer.children) {
- if (aNoTransition) {
- child.setAttribute("notransition", true);
- }
- child.style.transform = "";
- if (aNoTransition) {
- // Need to force a reflow otherwise this won't work.
- child.getBoundingClientRect();
- child.removeAttribute("notransition");
- }
- }
- // We snapped back, so we can assume there's no more
- // "last" placeholder insertion point to keep track of.
- if (aNoTransition) {
- this._lastPlaceholderInsertion = null;
- }
- },
-
- _getNextPos: function(aNode, aShiftDown, aSize) {
- // Shifting down is easy:
- if (this._inPanel && aShiftDown) {
- return "translate(0, " + aSize.height + "px)";
- }
- return this._diffWithNext(aNode, aSize);
- },
-
- _diffWithNext: function(aNode, aSize) {
- let xDiff;
- let yDiff = null;
- let nodeBounds = this._lazyStoreGet(aNode);
- let side = this._dir == "ltr" ? "left" : "right";
- let next = this._getVisibleSiblingForDirection(aNode, "next");
- // First we determine the transform along the x axis.
- // Usually, there will be a next node to base this on:
- if (next) {
- let otherBounds = this._lazyStoreGet(next);
- xDiff = otherBounds[side] - nodeBounds[side];
- // If the next node is a wide item in the panel, check if we could maybe
- // just move further out in the same row, without snapping to the next
- // one. This happens, for example, if moving an item that's before a wide
- // node within its own row of items. There will be space to drop this
- // item within the row, and the rest of the items do not need to shift.
- if (this.isWide(next)) {
- let otherXDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds,
- this._firstInRow(aNode));
- // If this has the same sign as our original shift, we're still
- // snapping to the start of the row. In this case, we should move
- // everything after us a row down, so as not to display two nodes on
- // top of each other:
- // (we would be able to get away with checking for equality instead of
- // equal signs here, but one of these is based on the x coordinate of
- // the first item in row N and one on that for row N - 1, so this is
- // safer, as their margins might differ)
- if ((otherXDiff < 0) == (xDiff < 0)) {
- this.__moveDown = true;
- } else {
- // Otherwise, we succeeded and can move further out. This also means
- // we can stop shifting the rest of the content:
- xDiff = otherXDiff;
- this.__undoShift = true;
- }
- } else {
- // We set this explicitly because otherwise some strange difference
- // between the height and the actual difference between line creeps in
- // and messes with alignments
- yDiff = otherBounds.top - nodeBounds.top;
- }
- } else {
- // We don't have a sibling whose position we can use. First, let's see
- // if we're also the first item (which complicates things):
- let firstNode = this._firstInRow(aNode);
- if (aNode == firstNode) {
- // Maybe we stored the horizontal distance between non-wide nodes,
- // if not, we'll use the width of the incoming node as a proxy:
- xDiff = this._horizontalDistance || aSize.width;
- } else {
- // If not, we should be able to get the distance to the previous node
- // and use the inverse, unless there's no room for another node (ie we
- // are the last node and there's no room for another one)
- xDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds, firstNode);
- }
- }
-
- // If we've not determined the vertical difference yet, check it here
- if (yDiff === null) {
- // If the next node is behind rather than in front, we must have moved
- // vertically:
- if ((xDiff > 0 && this._dir == "rtl") || (xDiff < 0 && this._dir == "ltr")) {
- yDiff = aSize.height;
- } else {
- // Otherwise, we haven't
- yDiff = 0;
- }
- }
- return "translate(" + xDiff + "px, " + yDiff + "px)";
- },
-
- /**
- * Helper function to find the transform a node if there isn't a next node
- * to base that on.
- * @param aNode the node to transform
- * @param aNodeBounds the bounding rect info of this node
- * @param aFirstNodeInRow the first node in aNode's row
- */
- _moveNextBasedOnPrevious: function(aNode, aNodeBounds, aFirstNodeInRow) {
- let next = this._getVisibleSiblingForDirection(aNode, "previous");
- let otherBounds = this._lazyStoreGet(next);
- let side = this._dir == "ltr" ? "left" : "right";
- let xDiff = aNodeBounds[side] - otherBounds[side];
- // If, however, this means we move outside the container's box
- // (i.e. the row in which this item is placed is full)
- // we should move it to align with the first item in the next row instead
- let bound = this._containerInfo[this._dir == "ltr" ? "right" : "left"];
- if ((this._dir == "ltr" && xDiff + aNodeBounds.right > bound) ||
- (this._dir == "rtl" && xDiff + aNodeBounds.left < bound)) {
- xDiff = this._lazyStoreGet(aFirstNodeInRow)[side] - aNodeBounds[side];
- }
- return xDiff;
- },
-
- /**
- * Get position details from our cache. If the node is not yet cached, get its position
- * information and cache it now.
- * @param aNode the node whose position info we want
- * @return the position info
- */
- _lazyStoreGet: function(aNode) {
- let rect = this._nodePositionStore.get(aNode);
- if (!rect) {
- // getBoundingClientRect() returns a DOMRect that is live, meaning that
- // as the element moves around, the rects values change. We don't want
- // that - we want a snapshot of what the rect values are right at this
- // moment, and nothing else. So we have to clone the values.
- let clientRect = aNode.getBoundingClientRect();
- rect = {
- left: clientRect.left,
- right: clientRect.right,
- width: clientRect.width,
- height: clientRect.height,
- top: clientRect.top,
- bottom: clientRect.bottom,
- };
- rect.x = rect.left + rect.width / 2;
- rect.y = rect.top + rect.height / 2;
- Object.freeze(rect);
- this._nodePositionStore.set(aNode, rect);
- }
- return rect;
- },
-
- _firstInRow: function(aNode) {
- // XXXmconley: I'm not entirely sure why we need to take the floor of these
- // values - it looks like, periodically, we're getting fractional pixels back
- // from lazyStoreGet. I've filed bug 994247 to investigate.
- let bound = Math.floor(this._lazyStoreGet(aNode).top);
- let rv = aNode;
- let prev;
- while (rv && (prev = this._getVisibleSiblingForDirection(rv, "previous"))) {
- if (Math.floor(this._lazyStoreGet(prev).bottom) <= bound) {
- return rv;
- }
- rv = prev;
- }
- return rv;
- },
-
- _getVisibleSiblingForDirection: function(aNode, aDirection) {
- let rv = aNode;
- do {
- rv = rv[aDirection + "Sibling"];
- } while (rv && rv.getAttribute("hidden") == "true")
- return rv;
- }
-}
-
-var DragPositionManager = {
- start: function(aWindow) {
- let areas = CustomizableUI.areas.filter((area) => CustomizableUI.getAreaType(area) != "toolbar");
- areas = areas.map((area) => CustomizableUI.getCustomizeTargetForArea(area, aWindow));
- areas.push(aWindow.document.getElementById(kPaletteId));
- for (let areaNode of areas) {
- let positionManager = gManagers.get(areaNode);
- if (positionManager) {
- positionManager.update(areaNode);
- } else {
- gManagers.set(areaNode, new AreaPositionManager(areaNode));
- }
- }
- },
-
- add: function(aWindow, aArea, aContainer) {
- if (CustomizableUI.getAreaType(aArea) != "toolbar") {
- return;
- }
-
- gManagers.set(aContainer, new AreaPositionManager(aContainer));
- },
-
- remove: function(aWindow, aArea, aContainer) {
- if (CustomizableUI.getAreaType(aArea) != "toolbar") {
- return;
- }
-
- gManagers.delete(aContainer);
- },
-
- stop: function() {
- gManagers = new WeakMap();
- },
-
- getManagerForArea: function(aArea) {
- return gManagers.get(aArea);
- }
-};
-
-Object.freeze(DragPositionManager);