Chromium Code Reviews| 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 5db42f1e7e6ea249c2cd453906f75a243df65b07..05b29959ea9eb2d112fac94706e4020420dbd5b0 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 closet 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); |
| @@ -119,13 +123,50 @@ var LayoutBehavior = { |
| this.highlightEdge_('', undefined); // Remove any highlights. |
| if (id != this.dragLayoutId || !this.dragBounds_) |
| 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 |
|
michaelpg
2016/06/29 20:16:50
nit: period
stevenjb
2016/06/29 23:25:30
Done.
|
| + 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() { |
| @@ -160,6 +201,100 @@ var LayoutBehavior = { |
| }, |
| /** |
| + * Re-parent all entries in |orphanIds| and any children. |
|
michaelpg
2016/06/29 20:16:51
nit: indicative ("Re-parents") here & below
stevenjb
2016/06/29 23:25:29
Done.
|
| + * @param {Array<string>} orphanIds The list of ids affected by the move. |
|
michaelpg
2016/06/29 20:16:51
nit: !Array<string>
stevenjb
2016/06/29 23:25:29
Done.
|
| + * @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.indexOf(o) == -1) |
|
michaelpg
2016/06/29 20:16:51
!orphans.includes(o)
also, consider using a Set i
stevenjb
2016/06/29 23:25:30
Done. (Order matters, see findChildren).
|
| + 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-parent 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); |
|
michaelpg
2016/06/29 20:16:51
opt nit: var layout = assert(...);
stevenjb
2016/06/29 23:25:30
I'm not a fan of that pattern outside of tests.
Dan Beam
2016/06/29 23:35:10
why?
stevenjb
2016/06/29 23:38:33
In C++ I've seen bugs where someone puts code with
michaelpg
2016/07/02 00:53:27
Acknowledged.
|
| + 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 locaiton and get the delta. |
|
michaelpg
2016/06/29 20:16:51
location
stevenjb
2016/06/29 23:25:30
Done.
|
| + 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 calculate the absolute bounds of a display. |
| * Caches the display bounds so that parent bounds are only calculated once. |
| * @param {string} id |
| @@ -259,22 +394,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); |
| @@ -298,11 +432,12 @@ var LayoutBehavior = { |
| /** |
| * Modifes |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; |
| @@ -323,8 +458,7 @@ var LayoutBehavior = { |
| y = this.snapToY_(bounds, parentBounds); |
| } |
| - bounds.left = x; |
| - bounds.top = y; |
| + return {x: x, y: y}; |
| }, |
| /** |
| @@ -506,6 +640,32 @@ var LayoutBehavior = { |
| }, |
| /** |
| + * Return the position of the corner of the div closest to |pos|. |
|
michaelpg
2016/06/29 20:16:51
nits: Return => Returns, position => bounds, pos =
stevenjb
2016/06/29 23:25:30
Rewrote.
|
| + * @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. |