Backed out changeset 77c3e8e02df4 (bug 919965) to fix commit msg

This commit is contained in:
Gijs Kruitbosch 2013-11-18 18:46:04 +01:00
parent 992d4ec18f
commit 9da15c701f
6 changed files with 206 additions and 560 deletions

View File

@ -1954,9 +1954,6 @@ this.CustomizableUI = {
get TYPE_MENU_PANEL() "menu-panel",
get TYPE_TOOLBAR() "toolbar",
get WIDE_PANEL_CLASS() "panel-wide-item",
get PANEL_COLUMN_COUNT() 3,
addListener: function(aListener) {
CustomizableUIInternal.addListener(aListener);
},

View File

@ -14,6 +14,10 @@ const kPaletteId = "customization-palette";
const kAboutURI = "about:customizing";
const kDragDataTypePrefix = "text/toolbarwrapper-id/";
const kPlaceholderClass = "panel-customization-placeholder";
const kWidePanelItemClass = "panel-wide-item";
// TODO(bug 885574): Merge this constant with the one in CustomizableWidgets.jsm,
// maybe just use a pref for this.
const kColumnsInMenuPanel = 3;
const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
Cu.import("resource://gre/modules/Services.jsm");
@ -21,8 +25,6 @@ Cu.import("resource:///modules/CustomizableUI.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DragPositionManager",
"resource:///modules/DragPositionManager.jsm");
let gModuleName = "[CustomizeMode]";
#include logging.js
@ -216,7 +218,6 @@ CustomizeMode.prototype = {
window.gNavToolbox.removeEventListener("toolbarvisibilitychange", this);
DragPositionManager.stop();
window.PanelUI.mainView.removeEventListener("contextmenu", this, true);
this.visiblePalette.removeEventListener("dragstart", this, true);
this.visiblePalette.removeEventListener("dragover", this, true);
@ -796,28 +797,22 @@ CustomizeMode.prototype = {
// Hack needed so that the dragimage will still show the
// item as it appeared before it was hidden.
this._initializeDragAfterMove = function() {
let win = aEvent.target.ownerDocument.defaultView;
win.setTimeout(function() {
// For automated tests, we sometimes start exiting customization mode
// before this fires, which leaves us with placeholders inserted after
// we've exited. So we need to check that we are indeed customizing.
if (this._customizing && !this._transitioning) {
item.hidden = true;
this._showPanelCustomizationPlaceholders();
DragPositionManager.start(this.window);
}
this._initializeDragAfterMove = null;
this.window.clearTimeout(this._dragInitializeTimeout);
}.bind(this);
this._dragInitializeTimeout = this.window.setTimeout(this._initializeDragAfterMove, 0);
}.bind(this), 0);
},
_onDragOver: function(aEvent) {
if (this._isUnwantedDragDrop(aEvent)) {
return;
}
if (this._initializeDragAfterMove) {
this._initializeDragAfterMove();
}
__dumpDragData(aEvent);
@ -850,8 +845,7 @@ CustomizeMode.prototype = {
return;
}
let targetIsToolbar = CustomizableUI.getAreaType(targetArea.id) == "toolbar";
let targetNode = this._getDragOverNode(aEvent, targetArea, targetIsToolbar, draggedItemId);
let targetNode = this._getDragOverNode(aEvent, targetArea);
// We need to determine the place that the widget is being dropped in
// the target.
@ -869,15 +863,20 @@ CustomizeMode.prototype = {
dragValue = "after";
} else {
dragOverItem = targetParent.children[position];
if (!targetIsToolbar) {
dragValue = "before";
// Check if the aDraggedItem is hovered past the first half of dragOverItem
let window = dragOverItem.ownerDocument.defaultView;
let direction = window.getComputedStyle(dragOverItem, null).direction;
let itemRect = dragOverItem.getBoundingClientRect();
let dropTargetCenter = itemRect.left + (itemRect.width / 2);
if (targetParent == window.PanelUI.contents) {
dragValue = "true";
if (direction == "ltr" && aEvent.clientX > dropTargetCenter) {
position++;
} else if (direction == "rtl" && aEvent.clientX < dropTargetCenter) {
position--;
}
dragOverItem = position == -1 ? targetParent.firstChild : targetParent.children[position];
} else {
// Check if the aDraggedItem is hovered past the first half of dragOverItem
let window = dragOverItem.ownerDocument.defaultView;
let direction = window.getComputedStyle(dragOverItem, null).direction;
let itemRect = dragOverItem.getBoundingClientRect();
let dropTargetCenter = itemRect.left + (itemRect.width / 2);
let existingDir = dragOverItem.getAttribute("dragover");
if ((existingDir == "before") == (direction == "ltr")) {
dropTargetCenter += (parseInt(dragOverItem.style.borderLeftWidth) || 0) / 2;
@ -891,12 +890,12 @@ CustomizeMode.prototype = {
}
if (this._dragOverItem && dragOverItem != this._dragOverItem) {
this._cancelDragActive(this._dragOverItem, dragOverItem);
this._setDragActive(this._dragOverItem, false);
}
if (dragOverItem != this._dragOverItem || dragValue != dragOverItem.getAttribute("dragover")) {
if (dragOverItem != targetArea.customizationTarget) {
this._setDragActive(dragOverItem, dragValue, draggedItemId, targetIsToolbar);
this._setDragActive(dragOverItem, dragValue, draggedItemId);
}
this._dragOverItem = dragOverItem;
}
@ -911,8 +910,6 @@ CustomizeMode.prototype = {
}
__dumpDragData(aEvent);
this._initializeDragAfterMove = null;
this.window.clearTimeout(this._dragInitializeTimeout);
let targetArea = this._getCustomizableParent(aEvent.currentTarget);
let document = aEvent.target.ownerDocument;
@ -921,8 +918,8 @@ CustomizeMode.prototype = {
aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0);
let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
let originArea = this._getCustomizableParent(draggedWrapper);
if (this._dragSizeMap) {
this._dragSizeMap.clear();
if (this._dragWidthMap) {
this._dragWidthMap.clear();
}
// Do nothing if the target area or origin area are not customizable.
if (!targetArea || !originArea) {
@ -946,7 +943,7 @@ CustomizeMode.prototype = {
targetNode = targetNode.firstChild;
}
this._cancelDragActive(this._dragOverItem, null, true);
this._setDragActive(this._dragOverItem, false);
this._removePanelCustomizationPlaceholders();
try {
@ -1070,14 +1067,8 @@ CustomizeMode.prototype = {
__dumpDragData(aEvent);
// When leaving customization areas, cancel the drag on the last dragover item
// We've attached the listener to areas, so aEvent.currentTarget will be the area.
// We don't care about dragexit events fired on descendants of the area,
// so we check that the event's target is the same as the area to which the listener
// was attached.
if (this._dragOverItem && aEvent.target == aEvent.currentTarget) {
this._cancelDragActive(this._dragOverItem);
this._dragOverItem = null;
if (this._dragOverItem) {
this._setDragActive(this._dragOverItem, false);
}
},
@ -1085,8 +1076,6 @@ CustomizeMode.prototype = {
if (this._isUnwantedDragDrop(aEvent)) {
return;
}
this._initializeDragAfterMove = null;
this.window.clearTimeout(this._dragInitializeTimeout);
__dumpDragData(aEvent);
let document = aEvent.target.ownerDocument;
@ -1125,131 +1114,176 @@ CustomizeMode.prototype = {
mozSourceNode.ownerDocument.defaultView != this.window;
},
_setDragActive: function(aItem, aValue, aDraggedItemId, aInToolbar) {
_setDragActive: function(aItem, aValue, aDraggedItemId) {
if (!aItem) {
return;
}
if (aItem.hasAttribute("dragover") != aValue) {
aItem.setAttribute("dragover", aValue);
if (aValue) {
if (aItem.hasAttribute("dragover") != aValue) {
aItem.setAttribute("dragover", aValue);
let window = aItem.ownerDocument.defaultView;
let draggedItem = window.document.getElementById(aDraggedItemId);
if (!aInToolbar) {
this._setPanelDragActive(aItem, draggedItem, aValue);
} else {
// Calculate width of the item when it'd be dropped in this position
let width = this._getDragItemSize(aItem, draggedItem).width;
let direction = window.getComputedStyle(aItem).direction;
let prop, otherProp;
// If we're inserting before in ltr, or after in rtl:
if ((aValue == "before") == (direction == "ltr")) {
prop = "borderLeftWidth";
otherProp = "border-right-width";
} else {
// otherwise:
prop = "borderRightWidth";
otherProp = "border-left-width";
let window = aItem.ownerDocument.defaultView;
let draggedItem = window.document.getElementById(aDraggedItemId);
let width = this._getDragItemWidth(aItem, draggedItem);
if (width) {
let panelContents = window.PanelUI.contents;
if (aItem.parentNode == panelContents) {
this._setPanelDragActive(aItem, draggedItem, width);
} else {
let direction = window.getComputedStyle(aItem).direction;
let prop, otherProp;
// If we're inserting before in ltr, or after in rtl:
if ((aValue == "before") == (direction == "ltr")) {
prop = "borderLeftWidth";
otherProp = "border-right-width";
} else {
// otherwise:
prop = "borderRightWidth";
otherProp = "border-left-width";
}
aItem.style[prop] = width;
aItem.style.removeProperty(otherProp);
}
}
aItem.style[prop] = width + 'px';
aItem.style.removeProperty(otherProp);
}
}
},
_cancelDragActive: function(aItem, aNextItem, aNoTransition) {
let currentArea = this._getCustomizableParent(aItem);
if (!currentArea) {
return;
}
let isToolbar = CustomizableUI.getAreaType(currentArea.id) == "toolbar";
if (isToolbar) {
} else {
aItem.removeAttribute("dragover");
// Remove both property values in the case that the end padding
// had been set.
aItem.style.removeProperty("border-left-width");
aItem.style.removeProperty("border-right-width");
} else {
if (aNextItem) {
let nextArea = this._getCustomizableParent(aNextItem);
if (nextArea == currentArea) {
// No need to do anything if we're still dragging in this area:
return;
}
}
// Otherwise, clear everything out:
let positionManager = DragPositionManager.getManagerForArea(currentArea);
positionManager.clearPlaceholders(currentArea, aNoTransition);
}
},
_setPanelDragActive: function(aDragOverNode, aDraggedItem, aValue) {
let targetArea = this._getCustomizableParent(aDragOverNode);
let positionManager = DragPositionManager.getManagerForArea(targetArea);
let draggedSize = this._getDragItemSize(aDragOverNode, aDraggedItem);
let isWide = aDraggedItem.classList.contains(CustomizableUI.WIDE_PANEL_CLASS);
positionManager.insertPlaceholder(targetArea, aDragOverNode, isWide, draggedSize);
_setPanelDragActive: function(aDragOverNode, aDraggedItem, aWidth) {
let document = aDragOverNode.ownerDocument;
let window = document.defaultView;
let panelContents = window.PanelUI.contents;
while (!aDragOverNode.id && aDragOverNode.parentNode != panelContents)
aDragOverNode = aDragOverNode.parentNode;
if (!aDragOverNode.id)
return;
if (!aDragOverNode.previousSibling ||
!aDragOverNode.previousSibling.classList.contains(kPlaceholderClass)) {
let isPlaceholderAtEnd = function(aPlaceholder) {
do {
aPlaceholder = aPlaceholder.nextSibling;
if (!aPlaceholder)
return true;
if (!aPlaceholder.classList.contains(kPlaceholderClass))
return false;
} while (aPlaceholder.nextSibling)
return true;
}
let resetAnimAttributes = function(aPlaceholder) {
if (!aPlaceholder)
return;
aPlaceholder.removeAttribute("expand");
aPlaceholder.removeAttribute("contract");
aPlaceholder.removeAttribute("hidden");
aPlaceholder.style.removeProperty("width");
}
let placeholders = Array.slice(panelContents.getElementsByClassName(kPlaceholderClass));
let toExpand = placeholders.shift();
let toContract = placeholders.shift();
if (toContract && isPlaceholderAtEnd(toContract))
toContract = null;
// Seek to find hidden placeholders first to use for the expand transition.
while (toExpand.getAttribute("hidden") != "true" && placeholders.length)
toExpand = placeholders.shift();
if (toExpand.transitioning || (toContract && toContract.transitioning))
return;
let wasHidden = (toContract && toContract.getAttribute("hidden") == "true") ||
toExpand.getAttribute("hidden") == "true";
resetAnimAttributes(toContract);
resetAnimAttributes(toExpand);
aDragOverNode.parentNode.insertBefore(toExpand, aDragOverNode);
toExpand.style.width = "0px";
toExpand.setAttribute("expand", "true");
toExpand.transitioning = true;
if (toContract) {
toContract.style.width = aWidth;
toContract.setAttribute("contract", "true");
toContract.transitioning = true;
}
window.mozRequestAnimationFrame(() => {
if (toContract)
toContract.style.width = "0px";
toExpand.style.width = aWidth;
});
toExpand.addEventListener("transitionend", function expandTransitionEnd() {
toExpand.removeEventListener("transitionend", expandTransitionEnd, false);
toExpand.transitioning = false;
});
if (toContract) {
toContract.addEventListener("transitionend", function contractTransitionEnd() {
toContract.removeEventListener("transitionend", contractTransitionEnd, false);
panelContents.appendChild(toContract);
if (wasHidden)
toContract.setAttribute("hidden", "true");
toContract.transitioning = false;
});
}
}
},
_getDragItemSize: function(aDragOverNode, aDraggedItem) {
_getDragItemWidth: function(aDragOverNode, aDraggedItem) {
// Cache it good, cache it real good.
if (!this._dragSizeMap)
this._dragSizeMap = new WeakMap();
if (!this._dragSizeMap.has(aDraggedItem))
this._dragSizeMap.set(aDraggedItem, new WeakMap());
let itemMap = this._dragSizeMap.get(aDraggedItem);
if (!this._dragWidthMap)
this._dragWidthMap = new WeakMap();
if (!this._dragWidthMap.has(aDraggedItem))
this._dragWidthMap.set(aDraggedItem, new WeakMap());
let itemMap = this._dragWidthMap.get(aDraggedItem);
let targetArea = this._getCustomizableParent(aDragOverNode);
let currentArea = this._getCustomizableParent(aDraggedItem);
// Return the size for this target from cache, if it exists.
let size = itemMap.get(targetArea);
if (size)
return size;
if (!targetArea)
return;
// Return the width for this target from cache, if it exists.
let width = itemMap.get(targetArea);
if (width)
return width;
// Calculate size of the item when it'd be dropped in this position.
// Calculate width of the item when it'd be dropped in this position.
let currentParent = aDraggedItem.parentNode;
let currentSibling = aDraggedItem.nextSibling;
// Move the widget temporarily next to the placeholder.
aDragOverNode.parentNode.insertBefore(aDraggedItem, aDragOverNode);
// Update the node's areaType.
let areaType = CustomizableUI.getAreaType(targetArea.id);
const kAreaType = "cui-areatype";
let areaType, currentType;
if (targetArea != currentArea) {
// Move the widget temporarily next to the placeholder.
aDragOverNode.parentNode.insertBefore(aDraggedItem, aDragOverNode);
// Update the node's areaType.
areaType = CustomizableUI.getAreaType(targetArea.id);
currentType = aDraggedItem.hasAttribute(kAreaType) &&
aDraggedItem.getAttribute(kAreaType);
if (areaType)
aDraggedItem.setAttribute(kAreaType, areaType);
this.wrapToolbarItem(aDraggedItem, areaType || "palette");
CustomizableUI.onWidgetDrag(aDraggedItem.id, targetArea.id);
} else {
aDraggedItem.parentNode.hidden = false;
}
// Fetch the new size.
let rect = aDraggedItem.parentNode.getBoundingClientRect();
size = {width: rect.width, height: rect.height};
// Cache the found value of size for this target.
itemMap.set(targetArea, size);
if (targetArea != currentArea) {
this.unwrapToolbarItem(aDraggedItem.parentNode);
// Put the item back into its previous position.
if (currentSibling)
currentParent.insertBefore(aDraggedItem, currentSibling);
let currentType = aDraggedItem.hasAttribute(kAreaType) &&
aDraggedItem.getAttribute(kAreaType);
if (areaType)
aDraggedItem.setAttribute(kAreaType, areaType);
CustomizableUI.onWidgetDrag(aDraggedItem.id, targetArea.id);
// Fetch the new width.
width = Math.floor(aDraggedItem.getBoundingClientRect().width) + "px";
// Put the item back into its previous position.
if (currentSibling)
currentParent.insertBefore(aDraggedItem, currentSibling);
else
currentParent.appendChild(aDraggedItem);
// restore the areaType
if (areaType) {
if (currentType === false)
aDraggedItem.removeAttribute(kAreaType);
else
currentParent.appendChild(aDraggedItem);
// restore the areaType
if (areaType) {
if (currentType === false)
aDraggedItem.removeAttribute(kAreaType);
else
aDraggedItem.setAttribute(kAreaType, currentType);
}
CustomizableUI.onWidgetDrag(aDraggedItem.id);
} else {
aDraggedItem.parentNode.hidden = true;
aDraggedItem.setAttribute(kAreaType, currentType);
}
return size;
CustomizableUI.onWidgetDrag(aDraggedItem.id);
// Cache the found value of width for this target.
itemMap.set(targetArea, width);
return width;
},
_getCustomizableParent: function(aElement) {
@ -1264,7 +1298,7 @@ CustomizeMode.prototype = {
return null;
},
_getDragOverNode: function(aEvent, aAreaElement, aInToolbar, aDraggedItemId) {
_getDragOverNode: function(aEvent, aAreaElement) {
let expectedParent = aAreaElement.customizationTarget || aAreaElement;
// Our tests are stupid. Cope:
if (!aEvent.clientX && !aEvent.clientY) {
@ -1280,16 +1314,9 @@ CustomizeMode.prototype = {
dragX = Math.min(bounds.right, Math.max(dragX, bounds.left));
dragY = Math.min(bounds.bottom, Math.max(dragY, bounds.top));
let targetNode;
if (aInToolbar) {
targetNode = aAreaElement.ownerDocument.elementFromPoint(dragX, dragY);
while (targetNode && targetNode.parentNode != expectedParent) {
targetNode = targetNode.parentNode;
}
} else {
let positionManager = DragPositionManager.getManagerForArea(aAreaElement);
// Find the closest node:
targetNode = positionManager.find(aAreaElement, dragX, dragY, aDraggedItemId);
let targetNode = aAreaElement.ownerDocument.elementFromPoint(dragX, dragY);
while (targetNode && targetNode.parentNode != expectedParent) {
targetNode = targetNode.parentNode;
}
return targetNode || aEvent.target;
},
@ -1299,7 +1326,7 @@ CustomizeMode.prototype = {
let doc = aEvent.target.ownerDocument;
doc.documentElement.setAttribute("customizing-movingItem", true);
let item = this._getWrapper(aEvent.target);
if (item && !item.classList.contains(kPlaceholderClass)) {
if (item) {
item.setAttribute("mousedown", "true");
}
},
@ -1324,37 +1351,35 @@ CustomizeMode.prototype = {
},
_showPanelCustomizationPlaceholders: function() {
this._removePanelCustomizationPlaceholders();
let doc = this.document;
let contents = this.panelUIContents;
let visibleWideItems = contents.querySelectorAll("toolbarpaletteitem:not([hidden]) > ." + kWidePanelItemClass);
let visibleChildren = contents.querySelectorAll("toolbarpaletteitem:not([hidden])");
// TODO(bug 885578): Still doesn't handle a hole when there is a wide
// widget located at the bottom of the panel.
let narrowItemsAfterWideItem = 0;
let node = contents.lastChild;
while (node && !node.classList.contains(CustomizableUI.WIDE_PANEL_CLASS) &&
(!node.firstChild || !node.firstChild.classList.contains(CustomizableUI.WIDE_PANEL_CLASS))) {
if (!node.hidden && !node.classList.contains(kPlaceholderClass)) {
while (node && !node.classList.contains(kWidePanelItemClass) &&
(!node.firstChild || !node.firstChild.classList.contains(kWidePanelItemClass))) {
if (!node.hidden) {
narrowItemsAfterWideItem++;
}
node = node.previousSibling;
}
let orphanedItems = narrowItemsAfterWideItem % CustomizableUI.PANEL_COLUMN_COUNT;
let placeholders = CustomizableUI.PANEL_COLUMN_COUNT - orphanedItems;
let orphanedItems = narrowItemsAfterWideItem % kColumnsInMenuPanel;
let placeholders = kColumnsInMenuPanel - orphanedItems;
let currentPlaceholderCount = contents.querySelectorAll("." + kPlaceholderClass).length;
if (placeholders > currentPlaceholderCount) {
while (placeholders-- > currentPlaceholderCount) {
let placeholder = doc.createElement("toolbarpaletteitem");
placeholder.classList.add(kPlaceholderClass);
//XXXjaws The toolbarbutton child here is only necessary to get
// the styling right here.
let placeholderChild = doc.createElement("toolbarbutton");
placeholderChild.classList.add(kPlaceholderClass + "-child");
placeholder.appendChild(placeholderChild);
contents.appendChild(placeholder);
}
} else if (placeholders < currentPlaceholderCount) {
while (placeholders++ < currentPlaceholderCount) {
contents.querySelectorAll("." + kPlaceholderClass)[0].remove();
}
while (placeholders--) {
let placeholder = doc.createElement("toolbarpaletteitem");
placeholder.classList.add(kPlaceholderClass);
//XXXjaws The toolbarbutton child here is only necessary to get
// the styling right here.
let placeholderChild = doc.createElement("toolbarbutton");
placeholderChild.classList.add(kPlaceholderClass + "-child");
placeholder.appendChild(placeholderChild);
contents.appendChild(placeholder);
}
},
@ -1384,9 +1409,6 @@ function getPlaceForItem(aElement) {
}
function __dumpDragData(aEvent, caller) {
if (!gDebug) {
return;
}
let str = "Dumping drag data (CustomizeMode.jsm) {\n";
str += " type: " + aEvent["type"] + "\n";
for (let el of ["target", "currentTarget", "relatedTarget"]) {

View File

@ -1,364 +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");
let 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.ownerDocument.defaultView;
this._dir = window.getComputedStyle(aContainer).direction;
let containerRect = aContainer.getBoundingClientRect();
this._containerInfo = {
left: containerRect.left,
right: containerRect.right,
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) {
let window = aContainer.ownerDocument.defaultView;
this._nodePositionStore = new WeakMap();
this._wideCache = new Set();
let last = null;
let singleItemHeight;
for (let child of aContainer.children) {
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;
for (let node of aContainer.children) {
let coordinates = this._lazyStoreGet(node);
let hDiff = coordinates.x - aX;
let vDiff = coordinates.y - 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 (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) {
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;
}
// 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 = "";
}
}
delete this.__moveDown;
delete this.__undoShift;
},
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");
}
}
},
_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) {
rect = aNode.getBoundingClientRect();
rect.x = rect.left + rect.width / 2;
rect.y = rect.top + rect.height / 2;
this._nodePositionStore.set(aNode, rect);
}
return rect;
},
_firstInRow: function(aNode) {
let bound = this._lazyStoreGet(aNode).top;
let rv = aNode;
let prev;
while (rv && (prev = this._getVisibleSiblingForDirection(rv, "previous"))) {
if (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;
}
}
let 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));
}
}
},
stop: function() {
gManagers.clear();
},
getManagerForArea: function(aArea) {
return gManagers.get(aArea);
}
};
Object.freeze(DragPositionManager);

View File

@ -23,6 +23,13 @@ let gWideWidgets = new Set();
// All the widgets we know of:
let gSeenWidgets = new Set();
// The class by which we recognize wide widgets:
const kWidePanelItemClass = "panel-wide-item";
// TODO(bug 885574): Merge this constant with the one in CustomizeMode.jsm,
// maybe just use a pref for this.
const kColumnsInMenuPanel = 3;
let PanelWideWidgetTracker = {
// Listeners used to validate panel contents whenever they change:
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
@ -54,7 +61,7 @@ let PanelWideWidgetTracker = {
// Furthermore, onWidgetCreated only fires for API-based widgets, not for XUL ones.
onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) {
if (!gSeenWidgets.has(aNode.id)) {
if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
if (aNode.classList.contains(kWidePanelItemClass)) {
gWideWidgets.add(aNode.id);
}
gSeenWidgets.add(aNode.id);
@ -130,12 +137,12 @@ let PanelWideWidgetTracker = {
}
}
if (fixedPos !== null || prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT) {
if (fixedPos !== null || prevSiblingCount % kColumnsInMenuPanel) {
let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId);
let desiredChange = -(prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT);
let desiredChange = -(prevSiblingCount % kColumnsInMenuPanel);
if (aMoveForwards && fixedPos == null) {
// +1 because otherwise we'd count ourselves:
desiredChange = CustomizableUI.PANEL_COLUMN_COUNT + desiredChange + 1;
desiredChange = kColumnsInMenuPanel + desiredChange + 1;
}
desiredPos += desiredChange;
CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos);

View File

@ -5,7 +5,6 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXTRA_JS_MODULES += [
'DragPositionManager.jsm',
'ScrollbarSampler.jsm',
]

View File

@ -115,21 +115,6 @@ toolbarpaletteitem[mousedown] {
opacity: 0.8;
}
.panel-customization-placeholder,
toolbarpaletteitem[place="palette"],
toolbarpaletteitem[place="panel"] {
transition: background-color, border-color, box-shadow, transform;
transition-duration: 10ms, 10ms, 10ms, 250ms;
transition-timing-function: linear, linear, linear, ease-in-out;
}
toolbarpaletteitem[notransition][place="palette"],
toolbarpaletteitem[notransition][place="panel"] {
transition: background-color, border-color, box-shadow;
transition-duration: 10ms, 10ms, 10ms;
transition-timing-function: linear, linear, linear;
}
toolbarpaletteitem > toolbarbutton > .toolbarbutton-icon {
transition: transform 50ms ease-in-out;
}