diff options
Diffstat (limited to 'application/basilisk/components/customizableui/DragPositionManager.jsm')
-rw-r--r-- | application/basilisk/components/customizableui/DragPositionManager.jsm | 420 |
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); |