Index: chrome/browser/resources/settings/device_page/layout_behavior.js |
diff --git a/chrome/browser/resources/settings/device_page/layout_behavior.js b/chrome/browser/resources/settings/device_page/layout_behavior.js |
index ccc2c32865f307aa268ada9ab91ba7a3a38fed0a..51ba51bd83342f9380b855e19f5a23c19e4c0dcc 100644 |
--- a/chrome/browser/resources/settings/device_page/layout_behavior.js |
+++ b/chrome/browser/resources/settings/device_page/layout_behavior.js |
@@ -80,10 +80,14 @@ var LayoutBehavior = { |
var closestId = this.findClosest_(id, newBounds); |
// Find the closest edge. |
- var layoutPosition = this.getLayoutPositionForBounds_(newBounds, closestId); |
+ var closestBounds = this.getCalculatedDisplayBounds(closestId); |
+ var layoutPosition = |
+ this.getLayoutPositionForBounds_(newBounds, closestBounds); |
// Snap to the closest edge. |
- this.snapBounds_(closestId, layoutPosition, newBounds); |
+ var snapPos = this.snapBounds_(newBounds, closestId, layoutPosition); |
+ newBounds.left = snapPos.x; |
+ newBounds.top = snapPos.y; |
// Calculate the new bounds and delta. |
var oldBounds = this.dragBounds_ || this.getCalculatedDisplayBounds(id); |
@@ -122,13 +126,50 @@ var LayoutBehavior = { |
!this.dragLayoutPosition_) { |
return; |
} |
+ |
var layout = this.displayLayoutMap_.get(id); |
- if (!layout) |
- return; |
- // Note: This updates layout in this.displayLayoutMap_ which is also the |
- // entry in this.layouts. |
- this.updateOffsetAndPosition_( |
- this.dragBounds_, this.dragLayoutPosition_, layout); |
+ |
+ var orphanIds; |
+ if (!layout || layout.parentId == '') { |
+ // Primary display. Set the calculated position to |dragBounds_|. |
+ this.setCalculatedDisplayBounds_(id, this.dragBounds_); |
+ |
+ // We cannot re-parent the primary display, so instead make all other |
+ // displays orphans and clear their calculated bounds. |
+ orphanIds = this.findChildren_(id, true /* recurse */); |
+ for (let o in orphanIds) |
+ this.calculatedBoundsMap_.delete(o); |
+ |
+ // Re-parent |dragParentId_|. It will be forced to parent to the dragged |
+ // display since it is the only non-orphan. |
+ this.reparentOrphan_(this.dragParentId_, orphanIds); |
+ orphanIds.splice(orphanIds.indexOf(this.dragParentId_), 1); |
+ } else { |
+ // All immediate children of |layout| will need to be re-parented. |
+ orphanIds = this.findChildren_(id, false /* do not recurse */); |
+ for (let o in orphanIds) |
+ this.calculatedBoundsMap_.delete(o); |
+ |
+ // When re-parenting to a descendant, also parent any immediate child to |
+ // drag display's current parent. |
+ var topLayout = this.displayLayoutMap_.get(this.dragParentId_); |
+ while (topLayout && topLayout.parentId != '') { |
+ if (topLayout.parentId == id) { |
+ topLayout.parentId = layout.parentId; |
+ break; |
+ } |
+ topLayout = this.displayLayoutMap_.get(topLayout.parentId); |
+ } |
+ |
+ // Re-parent the dragged display. |
+ layout.parentId = this.dragParentId_; |
+ this.updateOffsetAndPosition_( |
+ this.dragBounds_, this.dragLayoutPosition_, layout); |
+ } |
+ |
+ // Update any orphaned children. This may cause the dragged display to |
+ // be re-attached if it was attached to a child. |
+ this.updateOrphans_(orphanIds); |
// Send the updated layouts. |
chrome.system.display.setDisplayLayout(this.layouts, function() { |
@@ -163,7 +204,101 @@ var LayoutBehavior = { |
}, |
/** |
- * Recursively calculate the absolute bounds of a display. |
+ * Re-parents all entries in |orphanIds| and any children. |
+ * @param {!Array<string>} orphanIds The list of ids affected by the move. |
+ * @private |
+ */ |
+ updateOrphans_: function(orphanIds) { |
+ var orphans = orphanIds.slice(); |
+ for (let orphan of orphanIds) { |
+ var newOrphans = this.findChildren_(orphan, true /* recurse */); |
+ // If the dragged display was re-parented to one of its children, |
+ // there may be duplicates so merge the lists. |
+ for (let o of newOrphans) { |
+ if (!orphans.includes(o)) |
+ orphans.push(o); |
+ } |
+ } |
+ |
+ // Remove each orphan from the list as it is re-parented so that |
+ // subsequent orphans can be parented to it. |
+ while (orphans.length) { |
+ var orphanId = orphans.shift(); |
+ this.reparentOrphan_(orphanId, orphans); |
+ } |
+ }, |
+ |
+ /** |
+ * Re-parents the orphan to a layout that is not a member of |
+ * |otherOrphanIds|. |
+ * @param {string} orphanId The id of the orphan to re-parent. |
+ * @param {Array<string>} otherOrphanIds The list of ids of other orphans |
+ * to ignore when re-parenting. |
+ * @private |
+ */ |
+ reparentOrphan_: function(orphanId, otherOrphanIds) { |
+ var layout = this.displayLayoutMap_.get(orphanId); |
+ assert(layout); |
+ if (orphanId == this.dragId_ && layout.parentId != '') { |
+ this.setCalculatedDisplayBounds_(orphanId, this.dragBounds_); |
+ return; |
+ } |
+ var bounds = this.getCalculatedDisplayBounds(orphanId); |
+ |
+ // Find the closest parent. |
+ var newParentId = this.findClosest_(orphanId, bounds, otherOrphanIds); |
+ assert(newParentId != ''); |
+ layout.parentId = newParentId; |
+ |
+ // Find the closest edge. |
+ var parentBounds = this.getCalculatedDisplayBounds(newParentId); |
+ var layoutPosition = this.getLayoutPositionForBounds_(bounds, parentBounds); |
+ |
+ // Move from the nearest corner to the desired location and get the delta. |
+ var cornerBounds = this.getCornerBounds_(bounds, parentBounds); |
+ var desiredPos = this.snapBounds_(bounds, newParentId, layoutPosition); |
+ var deltaPos = { |
+ x: desiredPos.x - cornerBounds.left, |
+ y: desiredPos.y - cornerBounds.top |
+ }; |
+ |
+ // Check for collisions. |
+ this.collideAndModifyDelta_(orphanId, cornerBounds, deltaPos); |
+ var desiredBounds = { |
+ left: cornerBounds.left + deltaPos.x, |
+ top: cornerBounds.top + deltaPos.y, |
+ width: bounds.width, |
+ height: bounds.height |
+ }; |
+ |
+ this.updateOffsetAndPosition_(desiredBounds, layoutPosition, layout); |
+ }, |
+ |
+ /** |
+ * @param {string} parentId |
+ * @param {boolean} recurse Include descendants of children. |
+ * @return {!Array<string>} |
+ * @private |
+ */ |
+ findChildren_: function(parentId, recurse) { |
+ var children = []; |
+ for (let childId of this.displayLayoutMap_.keys()) { |
+ if (childId == parentId) |
+ continue; |
+ if (this.displayLayoutMap_.get(childId).parentId == parentId) { |
+ // Insert immediate children at the front of the array. |
+ children.unshift(childId); |
+ if (recurse) { |
+ // Descendants get added to the end of the list. |
+ children = children.concat(this.findChildren_(childId, true)); |
+ } |
+ } |
+ } |
+ return children; |
+ }, |
+ |
+ /** |
+ * Recursively calculates the absolute bounds of a display. |
* Caches the display bounds so that parent bounds are only calculated once. |
* @param {string} id |
* @param {number} width |
@@ -262,22 +397,21 @@ var LayoutBehavior = { |
/** |
* Calculates the LayoutPosition for |bounds| relative to |parentId|. |
* @param {!chrome.system.display.Bounds} bounds |
- * @param {string} parentId |
+ * @param {!chrome.system.display.Bounds} parentBounds |
* @return {!chrome.system.display.LayoutPosition} |
*/ |
- getLayoutPositionForBounds_: function(bounds, parentId) { |
+ getLayoutPositionForBounds_: function(bounds, parentBounds) { |
// Translate bounds from top-left to center. |
var x = bounds.left + bounds.width / 2; |
var y = bounds.top + bounds.height / 2; |
// Determine the distance from the new bounds to both of the near edges. |
- var parentBounds = this.getCalculatedDisplayBounds(parentId); |
var left = parentBounds.left; |
var top = parentBounds.top; |
var width = parentBounds.width; |
var height = parentBounds.height; |
- // Signed deltas to the center of the div. |
+ // Signed deltas to the center. |
var dx = x - (left + width / 2); |
var dy = y - (top + height / 2); |
@@ -299,13 +433,14 @@ var LayoutBehavior = { |
}, |
/** |
- * Modifes |bounds| to the position closest to it along the edge of |parentId| |
- * specified by |layoutPosition|. |
+ * Modifies |bounds| to the position closest to it along the edge of |
+ * |parentId| specified by |layoutPosition|. |
+ * @param {!chrome.system.display.Bounds} bounds |
* @param {string} parentId |
* @param {!chrome.system.display.LayoutPosition} layoutPosition |
- * @param {!chrome.system.display.Bounds} bounds |
+ * @return {!{x: number, y: number}} |
*/ |
- snapBounds_: function(parentId, layoutPosition, bounds) { |
+ snapBounds_: function(bounds, parentId, layoutPosition) { |
var parentBounds = this.getCalculatedDisplayBounds(parentId); |
var x; |
@@ -326,8 +461,7 @@ var LayoutBehavior = { |
y = this.snapToY_(bounds, parentBounds); |
} |
- bounds.left = x; |
- bounds.top = y; |
+ return {x: x, y: y}; |
}, |
/** |
@@ -510,6 +644,32 @@ var LayoutBehavior = { |
}, |
/** |
+ * Returns |bounds| translated to touch the closest corner of |parentBounds|. |
+ * @param {!chrome.system.display.Bounds} bounds |
+ * @param {!chrome.system.display.Bounds} parentBounds |
+ * @return {!chrome.system.display.Bounds} |
+ * @private |
+ */ |
+ getCornerBounds_: function(bounds, parentBounds) { |
+ var x; |
+ if (bounds.left > parentBounds.left + parentBounds.width / 2) |
+ x = parentBounds.left + parentBounds.width; |
+ else |
+ x = parentBounds.left - bounds.width; |
+ var y; |
+ if (bounds.top > parentBounds.top + parentBounds.height / 2) |
+ y = parentBounds.top + parentBounds.height; |
+ else |
+ y = parentBounds.top - bounds.height; |
+ return { |
+ left: x, |
+ top: y, |
+ width: bounds.width, |
+ height: bounds.height, |
+ }; |
+ }, |
+ |
+ /** |
* Highlights the edge of the div associated with |id| based on |
* |layoutPosition| and removes any other highlights. If |layoutPosition| is |
* undefined, removes all highlights. |