diff options
Diffstat (limited to 'chromium/chrome/browser/resources/settings/device_page/layout_behavior.js')
-rw-r--r-- | chromium/chrome/browser/resources/settings/device_page/layout_behavior.js | 727 |
1 files changed, 0 insertions, 727 deletions
diff --git a/chromium/chrome/browser/resources/settings/device_page/layout_behavior.js b/chromium/chrome/browser/resources/settings/device_page/layout_behavior.js deleted file mode 100644 index ae353b0c1ac..00000000000 --- a/chromium/chrome/browser/resources/settings/device_page/layout_behavior.js +++ /dev/null @@ -1,727 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/** - * @fileoverview Behavior for handling display layout, specifically - * edge snapping and collisions. - */ - -/** @polymerBehavior */ -const LayoutBehavior = { - properties: { - /** - * Array of display layouts. - * @type {!Array<!chrome.system.display.DisplayLayout>} - */ - layouts: Array, - - /** - * Whether or not mirroring is enabled. - * @type {boolean} - */ - mirroring: { - type: Boolean, - value: false, - }, - }, - - /** @private {!Map<string, chrome.system.display.Bounds>} */ - displayBoundsMap_: new Map(), - - /** @private {!Map<string, chrome.system.display.DisplayLayout>} */ - displayLayoutMap_: new Map(), - - /** - * The calculated bounds used for generating the div bounds. - * @private {!Map<string, chrome.system.display.Bounds>} - */ - calculatedBoundsMap_: new Map(), - - /** @private {string} */ - dragLayoutId_: '', - - /** @private {string} */ - dragParentId_: '', - - /** @private {!chrome.system.display.Bounds|undefined} */ - dragBounds_: undefined, - - /** @private {!chrome.system.display.LayoutPosition|undefined} */ - dragLayoutPosition_: undefined, - - /** - * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays - * @param {!Array<!chrome.system.display.DisplayLayout>} layouts - */ - initializeDisplayLayout: function(displays, layouts) { - this.dragLayoutId_ = ''; - this.dragParentId_ = ''; - - this.mirroring = displays.length > 0 && !!displays[0].mirroringSourceId; - - this.displayBoundsMap_.clear(); - for (const display of displays) { - this.displayBoundsMap_.set(display.id, display.bounds); - } - this.displayLayoutMap_.clear(); - for (const layout of layouts) { - this.displayLayoutMap_.set(layout.id, layout); - } - this.calculatedBoundsMap_.clear(); - for (const display of displays) { - if (!this.calculatedBoundsMap_.has(display.id)) { - const bounds = display.bounds; - this.calculateBounds_(display.id, bounds.width, bounds.height); - } - } - }, - - /** - * Called when a drag event occurs. Checks collisions and updates the layout. - * @param {string} id - * @param {!chrome.system.display.Bounds} newBounds The new calculated - * bounds for the display. - * @return {!chrome.system.display.Bounds} - */ - updateDisplayBounds: function(id, newBounds) { - this.dragLayoutId_ = id; - - // Find the closest parent. - const closestId = this.findClosest_(id, newBounds); - assert(closestId); - - // Find the closest edge. - const closestBounds = this.getCalculatedDisplayBounds(closestId); - const layoutPosition = - this.getLayoutPositionForBounds_(newBounds, closestBounds); - - // Snap to the closest edge. - const snapPos = this.snapBounds_(newBounds, closestId, layoutPosition); - newBounds.left = snapPos.x; - newBounds.top = snapPos.y; - - // Calculate the new bounds and delta. - const oldBounds = this.dragBounds_ || this.getCalculatedDisplayBounds(id); - const deltaPos = { - x: newBounds.left - oldBounds.left, - y: newBounds.top - oldBounds.top - }; - - // Check for collisions after snapping. This should not collide with the - // closest parent. - this.collideAndModifyDelta_(id, oldBounds, deltaPos); - - // If the edge changed, update and highlight it. - if (layoutPosition != this.dragLayoutPosition_ || - closestId != this.dragParentId_) { - this.dragLayoutPosition_ = layoutPosition; - this.dragParentId_ = closestId; - this.highlightEdge_(closestId, layoutPosition); - } - - newBounds.left = oldBounds.left + deltaPos.x; - newBounds.top = oldBounds.top + deltaPos.y; - - this.dragBounds_ = newBounds; - - return newBounds; - }, - - /** - * Called when dragging ends. Sends the updated layout to chrome. - * @param {string} id - */ - finishUpdateDisplayBounds: function(id) { - this.highlightEdge_('', undefined); // Remove any highlights. - if (id != this.dragLayoutId_ || !this.dragBounds_ || - !this.dragLayoutPosition_) { - return; - } - - const layout = this.displayLayoutMap_.get(id); - - let 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 */); - - // 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 */); - - // When re-parenting to a descendant, also parent any immediate child to - // drag display's current parent. - let 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() { - if (chrome.runtime.lastError) { - console.error( - 'setDisplayLayout Error: ' + chrome.runtime.lastError.message); - } - }); - }, - - /** - * @param {string} displayId - * @param {boolean=} opt_notest Set to true if bounds may not be set. - * @return {!chrome.system.display.Bounds} bounds - */ - getCalculatedDisplayBounds: function(displayId, opt_notest) { - const bounds = this.calculatedBoundsMap_.get(displayId); - assert(opt_notest || bounds); - return bounds; - }, - - /** - * @param {string} displayId - * @param {!chrome.system.display.Bounds|undefined} bounds - * @private - */ - setCalculatedDisplayBounds_: function(displayId, bounds) { - assert(bounds); - this.calculatedBoundsMap_.set( - displayId, - /** @type {!chrome.system.display.Bounds} */ - (Object.assign({}, bounds))); - }, - - /** - * 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) { - const orphans = orphanIds.slice(); - for (let i = 0; i < orphanIds.length; ++i) { - const orphan = orphanIds[i]; - const 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 ii = 0; ii < newOrphans.length; ++ii) { - const o = newOrphans[ii]; - 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) { - const 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) { - const layout = this.displayLayoutMap_.get(orphanId); - assert(layout); - if (orphanId == this.dragId && layout.parentId != '') { - this.setCalculatedDisplayBounds_(orphanId, this.dragBounds_); - return; - } - const bounds = this.getCalculatedDisplayBounds(orphanId); - - // Find the closest parent. - const newParentId = this.findClosest_(orphanId, bounds, otherOrphanIds); - assert(newParentId != ''); - layout.parentId = newParentId; - - // Find the closest edge. - const parentBounds = this.getCalculatedDisplayBounds(newParentId); - const layoutPosition = - this.getLayoutPositionForBounds_(bounds, parentBounds); - - // Move from the nearest corner to the desired location and get the delta. - const cornerBounds = this.getCornerBounds_(bounds, parentBounds); - const desiredPos = this.snapBounds_(bounds, newParentId, layoutPosition); - const deltaPos = { - x: desiredPos.x - cornerBounds.left, - y: desiredPos.y - cornerBounds.top - }; - - // Check for collisions. - this.collideAndModifyDelta_(orphanId, cornerBounds, deltaPos); - const 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) { - let children = []; - this.displayLayoutMap_.forEach((value, key) => { - const childId = key; - if (childId != parentId && value.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 - * @param {number} height - * @private - */ - calculateBounds_: function(id, width, height) { - let left, top; - const layout = this.displayLayoutMap_.get(id); - if (this.mirroring || !layout || !layout.parentId) { - left = -width / 2; - top = -height / 2; - } else { - if (!this.calculatedBoundsMap_.has(layout.parentId)) { - const pbounds = this.displayBoundsMap_.get(layout.parentId); - this.calculateBounds_(layout.parentId, pbounds.width, pbounds.height); - } - const parentBounds = this.getCalculatedDisplayBounds(layout.parentId); - left = parentBounds.left; - top = parentBounds.top; - switch (layout.position) { - case chrome.system.display.LayoutPosition.TOP: - left += layout.offset; - top -= height; - break; - case chrome.system.display.LayoutPosition.RIGHT: - left += parentBounds.width; - top += layout.offset; - break; - case chrome.system.display.LayoutPosition.BOTTOM: - left += layout.offset; - top += parentBounds.height; - break; - case chrome.system.display.LayoutPosition.LEFT: - left -= width; - top += layout.offset; - break; - } - } - const result = { - left: left, - top: top, - width: width, - height: height, - }; - this.setCalculatedDisplayBounds_(id, result); - }, - - /** - * Finds the display closest to |bounds| ignoring |opt_ignoreIds|. - * @param {string} displayId - * @param {!chrome.system.display.Bounds} bounds - * @param {Array<string>=} opt_ignoreIds Ids to ignore. - * @return {string} - * @private - */ - findClosest_: function(displayId, bounds, opt_ignoreIds) { - const x = bounds.left + bounds.width / 2; - const y = bounds.top + bounds.height / 2; - let closestId = ''; - let closestDelta2 = 0; - const keys = this.calculatedBoundsMap_.keys(); - for (let iter = keys.next(); !iter.done; iter = keys.next()) { - const otherId = iter.value; - if (otherId == displayId) { - continue; - } - if (opt_ignoreIds && opt_ignoreIds.includes(otherId)) { - continue; - } - const otherBounds = this.getCalculatedDisplayBounds(otherId); - const left = otherBounds.left; - const top = otherBounds.top; - const width = otherBounds.width; - const height = otherBounds.height; - if (x >= left && x < left + width && y >= top && y < top + height) { - return otherId; - } // point is inside rect - let dx, dy; - if (x < left) { - dx = left - x; - } else if (x > left + width) { - dx = x - (left + width); - } else { - dx = 0; - } - if (y < top) { - dy = top - y; - } else if (y > top + height) { - dy = y - (top + height); - } else { - dy = 0; - } - const delta2 = dx * dx + dy * dy; - if (closestId == '' || delta2 < closestDelta2) { - closestId = otherId; - closestDelta2 = delta2; - } - } - return closestId; - }, - - /** - * Calculates the LayoutPosition for |bounds| relative to |parentId|. - * @param {!chrome.system.display.Bounds} bounds - * @param {!chrome.system.display.Bounds} parentBounds - * @return {!chrome.system.display.LayoutPosition} - */ - getLayoutPositionForBounds_: function(bounds, parentBounds) { - // Translate bounds from top-left to center. - const x = bounds.left + bounds.width / 2; - const y = bounds.top + bounds.height / 2; - - // Determine the distance from the new bounds to both of the near edges. - const left = parentBounds.left; - const top = parentBounds.top; - const width = parentBounds.width; - const height = parentBounds.height; - - // Signed deltas to the center. - const dx = x - (left + width / 2); - const dy = y - (top + height / 2); - - // Unsigned distance to each edge. - const distx = Math.abs(dx) - width / 2; - const disty = Math.abs(dy) - height / 2; - - if (distx > disty) { - if (dx < 0) { - return chrome.system.display.LayoutPosition.LEFT; - } else { - return chrome.system.display.LayoutPosition.RIGHT; - } - } else { - if (dy < 0) { - return chrome.system.display.LayoutPosition.TOP; - } else { - return chrome.system.display.LayoutPosition.BOTTOM; - } - } - }, - - /** - * 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 - * @return {!{x: number, y: number}} - */ - snapBounds_: function(bounds, parentId, layoutPosition) { - const parentBounds = this.getCalculatedDisplayBounds(parentId); - - let x; - if (layoutPosition == chrome.system.display.LayoutPosition.LEFT) { - x = parentBounds.left - bounds.width; - } else if (layoutPosition == chrome.system.display.LayoutPosition.RIGHT) { - x = parentBounds.left + parentBounds.width; - } else { - x = this.snapToX_(bounds, parentBounds); - } - - let y; - if (layoutPosition == chrome.system.display.LayoutPosition.TOP) { - y = parentBounds.top - bounds.height; - } else if (layoutPosition == chrome.system.display.LayoutPosition.BOTTOM) { - y = parentBounds.top + parentBounds.height; - } else { - y = this.snapToY_(bounds, parentBounds); - } - - return {x: x, y: y}; - }, - - /** - * Snaps a horizontal value, see snapToEdge. - * @param {!chrome.system.display.Bounds} newBounds - * @param {!chrome.system.display.Bounds} parentBounds - * @param {number=} opt_snapDistance Provide to override the snap distance. - * 0 means snap from any distance. - * @return {number} - */ - snapToX_: function(newBounds, parentBounds, opt_snapDistance) { - return this.snapToEdge_( - newBounds.left, newBounds.width, parentBounds.left, parentBounds.width, - opt_snapDistance); - }, - - /** - * Snaps a vertical value, see snapToEdge. - * @param {!chrome.system.display.Bounds} newBounds - * @param {!chrome.system.display.Bounds} parentBounds - * @param {number=} opt_snapDistance Provide to override the snap distance. - * 0 means snap from any distance. - * @return {number} - */ - snapToY_: function(newBounds, parentBounds, opt_snapDistance) { - return this.snapToEdge_( - newBounds.top, newBounds.height, parentBounds.top, parentBounds.height, - opt_snapDistance); - }, - - /** - * Snaps the region [point, width] to [basePoint, baseWidth] if - * the [point, width] is close enough to the base's edge. - * @param {number} point The starting point of the region. - * @param {number} width The width of the region. - * @param {number} basePoint The starting point of the base region. - * @param {number} baseWidth The width of the base region. - * @param {number=} opt_snapDistance Provide to override the snap distance. - * 0 means snap at any distance. - * @return {number} The moved point. Returns the point itself if it doesn't - * need to snap to the edge. - * @private - */ - snapToEdge_: function(point, width, basePoint, baseWidth, opt_snapDistance) { - // If the edge of the region is smaller than this, it will snap to the - // base's edge. - const SNAP_DISTANCE_PX = 16; - const snapDist = - (opt_snapDistance !== undefined) ? opt_snapDistance : SNAP_DISTANCE_PX; - - const startDiff = Math.abs(point - basePoint); - const endDiff = Math.abs(point + width - (basePoint + baseWidth)); - // Prefer the closer one if both edges are close enough. - if ((!snapDist || startDiff < snapDist) && startDiff < endDiff) { - return basePoint; - } else if (!snapDist || endDiff < snapDist) { - return basePoint + baseWidth - width; - } - - return point; - }, - - /** - * Intersects |layout| with each other layout and reduces |deltaPos| to - * avoid any collisions (or sets it to [0,0] if the display can not be moved - * in the direction of |deltaPos|). - * Note: this assumes that deltaPos is already 'snapped' to the parent edge, - * and therefore will not collide with the parent, i.e. this is to prevent - * overlapping with displays other than the parent. - * @param {string} id - * @param {!chrome.system.display.Bounds} bounds - * @param {!{x: number, y: number}} deltaPos - */ - collideAndModifyDelta_: function(id, bounds, deltaPos) { - const keys = this.calculatedBoundsMap_.keys(); - const others = new Set(keys); - others.delete(id); - let checkCollisions = true; - while (checkCollisions) { - checkCollisions = false; - const othersValues = others.values(); - for (let iter = othersValues.next(); !iter.done; - iter = othersValues.next()) { - const otherId = iter.value; - const otherBounds = this.getCalculatedDisplayBounds(otherId); - if (this.collideWithBoundsAndModifyDelta_( - bounds, otherBounds, deltaPos)) { - if (deltaPos.x == 0 && deltaPos.y == 0) { - return; - } - others.delete(otherId); - checkCollisions = true; - break; - } - } - } - }, - - /** - * Intersects |bounds| with |otherBounds|. If there is a collision, modifies - * |deltaPos| to limit movement to a single axis and avoid the collision - * and returns true. See note for |collideAndModifyDelta_|. - * @param {!chrome.system.display.Bounds} bounds - * @param {!chrome.system.display.Bounds} otherBounds - * @param {!{x: number, y: number}} deltaPos - * @return {boolean} Whether there was a collision. - */ - collideWithBoundsAndModifyDelta_: function(bounds, otherBounds, deltaPos) { - const newX = bounds.left + deltaPos.x; - const newY = bounds.top + deltaPos.y; - - if ((newX + bounds.width <= otherBounds.left) || - (newX >= otherBounds.left + otherBounds.width) || - (newY + bounds.height <= otherBounds.top) || - (newY >= otherBounds.top + otherBounds.height)) { - return false; - } - - // |deltaPos| should already be restricted to X or Y. This shortens the - // delta to stay outside the bounds, however it does not change the sign of - // the delta, i.e. it does not "push" the point outside the bounds if - // the point is already inside. - if (Math.abs(deltaPos.x) > Math.abs(deltaPos.y)) { - deltaPos.y = 0; - let snapDeltaX; - if (deltaPos.x > 0) { - snapDeltaX = - Math.max(0, (otherBounds.left - bounds.width) - bounds.left); - } else { - snapDeltaX = - Math.min(0, (otherBounds.left + otherBounds.width) - bounds.left); - } - deltaPos.x = snapDeltaX; - } else { - deltaPos.x = 0; - let snapDeltaY; - if (deltaPos.y > 0) { - snapDeltaY = - Math.min(0, (otherBounds.top - bounds.height) - bounds.top); - } else if (deltaPos.y < 0) { - snapDeltaY = - Math.max(0, (otherBounds.top + otherBounds.height) - bounds.top); - } else { - snapDeltaY = 0; - } - deltaPos.y = snapDeltaY; - } - - return true; - }, - - /** - * Updates the offset for |layout| from |bounds|. - * @param {!chrome.system.display.Bounds} bounds - * @param {!chrome.system.display.LayoutPosition} position - * @param {!chrome.system.display.DisplayLayout} layout - */ - updateOffsetAndPosition_: function(bounds, position, layout) { - layout.position = position; - if (!layout.parentId) { - layout.offset = 0; - return; - } - - // Offset is calculated from top or left edge. - const parentBounds = this.getCalculatedDisplayBounds(layout.parentId); - let offset, minOffset, maxOffset; - if (position == chrome.system.display.LayoutPosition.LEFT || - position == chrome.system.display.LayoutPosition.RIGHT) { - offset = bounds.top - parentBounds.top; - minOffset = -bounds.height; - maxOffset = parentBounds.height; - } else { - offset = bounds.left - parentBounds.left; - minOffset = -bounds.width; - maxOffset = parentBounds.width; - } - const MIN_OFFSET_OVERLAP = 50; - minOffset += MIN_OFFSET_OVERLAP; - maxOffset -= MIN_OFFSET_OVERLAP; - layout.offset = Math.max(minOffset, Math.min(offset, maxOffset)); - - // Update the calculated bounds to match the new offset. - this.calculateBounds_(layout.id, bounds.width, bounds.height); - }, - - /** - * 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) { - let x; - if (bounds.left > parentBounds.left + parentBounds.width / 2) { - x = parentBounds.left + parentBounds.width; - } else { - x = parentBounds.left - bounds.width; - } - let 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. - * @param {string} id - * @param {chrome.system.display.LayoutPosition|undefined} layoutPosition - * @private - */ - highlightEdge_: function(id, layoutPosition) { - for (let i = 0; i < this.layouts.length; ++i) { - const layout = this.layouts[i]; - const highlight = (layout.id == id) ? layoutPosition : undefined; - const div = this.$$('#_' + layout.id); - div.classList.toggle( - 'highlight-right', - highlight == chrome.system.display.LayoutPosition.RIGHT); - div.classList.toggle( - 'highlight-left', - highlight == chrome.system.display.LayoutPosition.LEFT); - div.classList.toggle( - 'highlight-top', - highlight == chrome.system.display.LayoutPosition.TOP); - div.classList.toggle( - 'highlight-bottom', - highlight == chrome.system.display.LayoutPosition.BOTTOM); - } - }, -}; |