Index: chrome/browser/resources/settings/device_page/display_layout.js |
diff --git a/chrome/browser/resources/settings/device_page/display_layout.js b/chrome/browser/resources/settings/device_page/display_layout.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5004a5db05345dd6436165912e10d3e70ea133d0 |
--- /dev/null |
+++ b/chrome/browser/resources/settings/device_page/display_layout.js |
@@ -0,0 +1,244 @@ |
+// 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 |
+ * 'display-layout' presents a visual representation of the layout of one or |
+ * more displays and allows them to be arranged. |
+ */ |
+ |
+(function() { |
+ |
+/** @const {number} */ var MIN_VISUAL_SCALE = .01; |
+ |
+Polymer({ |
+ is: 'display-layout', |
+ |
+ behaviors: [ |
+ Polymer.IronResizableBehavior, |
+ ], |
+ |
+ properties: { |
+ /** |
+ * Array of displays. |
+ * @type {!Array<!chrome.system.display.DisplayUnitInfo>} |
+ */ |
+ displays: Array, |
+ |
+ /** |
+ * Array of display layouts. |
+ * @type {!Array<!chrome.system.display.DisplayLayout>} |
+ */ |
+ layouts: Array, |
+ |
+ /** @type {!chrome.system.display.DisplayUnitInfo|undefined} */ |
+ selectedDisplay: Object, |
+ |
+ /** |
+ * The ratio of the display area div (in px) to DisplayUnitInfo.bounds. |
+ * @type {number} |
+ */ |
+ visualScale: 1, |
+ }, |
+ |
+ /** @private {!Object<chrome.system.display.DisplayUnitInfo>} */ |
+ displayMap_: {}, |
+ |
+ /** @private {!Object<chrome.system.display.DisplayLayout>} */ |
+ layoutMap_: {}, |
+ |
+ /** @private {!Object<chrome.system.display.Bounds>} */ |
+ boundsMap_: {}, |
+ |
+ /** @private {!{left: number, top: number}} */ |
+ visualOffset_: {left: 0, top: 0}, |
+ |
+ /** @override */ |
+ attached: function() { |
+ // TODO(stevenjb): Remove retry once fixed: |
+ // https://github.com/Polymer/polymer/issues/3629 |
+ var self = this; |
+ var retry = 100; // ms |
+ function tryCalcVisualScale() { |
+ if (!self.calculateVisualScale_()) |
+ setTimeout(tryCalcVisualScale, retry); |
+ } |
+ tryCalcVisualScale(); |
+ }, |
+ |
+ /** |
+ * Called explicitly when |this.displays| and their associated |this.layouts| |
+ * have been fetched from chrome. |
+ * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays |
+ * @param {!Array<!chrome.system.display.DisplayLayout>} layouts |
+ */ |
+ updateDisplays: function(displays, layouts) { |
+ this.displays = displays; |
+ this.layouts = layouts; |
+ |
+ this.displayMap_ = {}; |
+ for (let display of this.displays) |
+ this.displayMap_[display.id] = display; |
+ |
+ this.layoutMap_ = {}; |
+ for (let layout of this.layouts) |
+ this.layoutMap_[layout.id] = layout; |
+ |
+ this.boundsMap_ = {}; |
+ for (let display of this.displays) |
+ this.calcDisplayBounds_(display); |
+ |
+ this.calculateVisualScale_(); |
+ }, |
+ |
+ /** |
+ * Calculates the visual offset and scale for the display area |
+ * (i.e. the ratio of the display area div size to the area required to |
+ * contain the DisplayUnitInfo bounding boxes). |
+ * @return {boolean} Whether the calculation was successful. |
+ * @private |
+ */ |
+ calculateVisualScale_() { |
+ var displayAreaDiv = this.$.displayArea; |
+ if (!displayAreaDiv || !displayAreaDiv.offsetWidth || !this.displays || |
+ !this.displays.length) { |
+ return false; |
+ } |
+ |
+ var display = this.displays[0]; |
+ var bounds = this.boundsMap_[display.id]; |
+ var displayInfoBoundingBox = { |
+ left: bounds.left, |
+ right: bounds.left + bounds.width, |
+ top: bounds.top, |
+ bottom: bounds.top + bounds.height, |
+ }; |
+ var maxWidth = bounds.width; |
+ var maxHeight = bounds.height; |
+ for (let i = 1; i < this.displays.length; ++i) { |
+ display = this.displays[i]; |
+ bounds = this.boundsMap_[display.id]; |
+ displayInfoBoundingBox.left = |
+ Math.min(displayInfoBoundingBox.left, bounds.left); |
+ displayInfoBoundingBox.right = |
+ Math.max(displayInfoBoundingBox.right, bounds.left + bounds.width); |
+ displayInfoBoundingBox.top = |
+ Math.min(displayInfoBoundingBox.top, bounds.top); |
+ displayInfoBoundingBox.bottom = |
+ Math.max(displayInfoBoundingBox.bottom, bounds.top + bounds.height); |
+ maxWidth = Math.max(maxWidth, bounds.width); |
+ maxHeight = Math.max(maxHeight, bounds.height); |
+ } |
+ |
+ // Create a margin around the bounding box equal to the size of the |
+ // largest displays. |
+ var displayInfoBoundsWidth = displayInfoBoundingBox.right - |
+ displayInfoBoundingBox.left + maxWidth * 2; |
+ var displayInfoBoundsHeight = displayInfoBoundingBox.bottom - |
+ displayInfoBoundingBox.top + maxHeight * 2; |
+ |
+ // Calculate the scale. |
+ var horizontalScale = displayAreaDiv.offsetWidth / displayInfoBoundsWidth; |
+ var verticalScale = displayAreaDiv.offsetHeight / displayInfoBoundsHeight; |
+ var scale = Math.min(horizontalScale, verticalScale); |
+ |
+ // Calculate the offset. |
+ this.visualOffset_.left = (-displayInfoBoundingBox.left + maxWidth) * scale; |
+ this.visualOffset_.top = (-displayInfoBoundingBox.top + maxHeight) * scale; |
+ |
+ // Update the scale which will trigger calls to getDivStyle_. |
+ this.visualScale = Math.max(MIN_VISUAL_SCALE, scale); |
+ |
+ return true; |
+ }, |
+ |
+ /** |
+ * @param {!chrome.system.display.DisplayUnitInfo} display |
+ * @param {number} visualScale |
+ * @return {string} The style string for the div. |
+ * @private |
+ */ |
+ getDivStyle_: function(display, visualScale) { |
+ // This matches the size of the box-shadow or border in CSS. |
+ /** @const {number} */ var BORDER = 2; |
+ var bounds = this.boundsMap_[display.id]; |
+ var height = Math.round(bounds.height * this.visualScale) - BORDER * 2; |
+ var width = Math.round(bounds.width * this.visualScale) - BORDER * 2; |
+ var left = |
+ Math.round(this.visualOffset_.left + (bounds.left * this.visualScale)); |
+ var top = |
+ Math.round(this.visualOffset_.top + (bounds.top * this.visualScale)); |
+ return `height: ${height}px; width: ${width}px;` + |
+ ` left: ${left}px; top: ${top}px`; |
+ }, |
+ |
+ /** |
+ * @param {!chrome.system.display.DisplayUnitInfo} display |
+ * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
+ * @return {boolean} |
+ * @private |
+ */ |
+ isSelected_: function(display, selectedDisplay) { |
+ return display.id == selectedDisplay.id; |
+ }, |
+ |
+ /** |
+ * @param {!{model: !{index: number}}} e |
+ * @private |
+ */ |
+ onSelectDisplayTap_: function(e) { |
+ this.fire('select-display', e.model.index); |
+ }, |
+ |
+ /** |
+ * Recursively calculate the bounds of a display relative to its parents. |
+ * Caches the display bounds so that parent bounds are only calculated once. |
+ * TODO(stevenjb): Move this function and the maps it requires to a separate |
+ * behavior which will include snapping and collisions. |
+ * @param {!chrome.system.display.DisplayUnitInfo} display |
+ * @private |
+ */ |
+ calcDisplayBounds_: function(display) { |
+ if (display.id in this.boundsMap_) |
+ return; // Already calculated (i.e. a parent of a previous display) |
+ var left, top; |
+ if (display.isPrimary) { |
+ left = -display.bounds.width / 2; |
+ top = -display.bounds.height / 2; |
+ } else { |
+ var layout = this.layoutMap_[display.id]; |
+ assert(layout.parentId); |
+ var parentDisplay = this.displayMap_[layout.parentId]; |
+ var parentBounds; |
+ if (!(parentDisplay.id in this.boundsMap_)) |
+ this.calcDisplayBounds_(parentDisplay); |
+ parentBounds = this.boundsMap_[parentDisplay.id]; |
+ left = parentBounds.left; |
+ top = parentBounds.top; |
+ switch (layout.position) { |
+ case chrome.system.display.LayoutPosition.TOP: |
+ top -= display.bounds.height; |
+ break; |
+ case chrome.system.display.LayoutPosition.RIGHT: |
+ left += parentBounds.width; |
+ break; |
+ case chrome.system.display.LayoutPosition.BOTTOM: |
+ top += parentBounds.height; |
+ break; |
+ case chrome.system.display.LayoutPosition.LEFT: |
+ left -= display.bounds.height; |
+ break; |
+ } |
+ } |
+ var result = { |
+ left: left, |
+ top: top, |
+ width: display.bounds.width, |
+ height: display.bounds.height |
+ }; |
+ this.boundsMap_[display.id] = result; |
+ } |
+}); |
+ |
+})(); |