mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-16 20:27:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
420
browser/components/customizableui/DragPositionManager.jsm
Normal file
420
browser/components/customizableui/DragPositionManager.jsm
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
/* 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);
|
||||
Loading…
Add table
Add a link
Reference in a new issue