OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 /** |
| 6 * @fileoverview |
| 7 * 'display-layout' presents a visual representation of the layout of one or |
| 8 * more displays and allows them to be arranged. |
| 9 */ |
| 10 |
| 11 (function() { |
| 12 |
| 13 /** @const {number} */ var MIN_VISUAL_SCALE = .01; |
| 14 |
| 15 Polymer({ |
| 16 is: 'display-layout', |
| 17 |
| 18 behaviors: [ |
| 19 Polymer.IronResizableBehavior, |
| 20 ], |
| 21 |
| 22 properties: { |
| 23 /** |
| 24 * Array of displays. |
| 25 * @type {!Array<!chrome.system.display.DisplayUnitInfo>} |
| 26 */ |
| 27 displays: Array, |
| 28 |
| 29 /** |
| 30 * Array of display layouts. |
| 31 * @type {!Array<!chrome.system.display.DisplayLayout>} |
| 32 */ |
| 33 layouts: Array, |
| 34 |
| 35 /** @type {!chrome.system.display.DisplayUnitInfo|undefined} */ |
| 36 selectedDisplay: Object, |
| 37 |
| 38 /** |
| 39 * The ratio of the display area div (in px) to DisplayUnitInfo.bounds. |
| 40 * @type {number} |
| 41 */ |
| 42 visualScale: 1, |
| 43 }, |
| 44 |
| 45 /** @private {!Object<chrome.system.display.DisplayUnitInfo>} */ |
| 46 displayMap_: {}, |
| 47 |
| 48 /** @private {!Object<chrome.system.display.DisplayLayout>} */ |
| 49 layoutMap_: {}, |
| 50 |
| 51 /** @private {!Object<chrome.system.display.Bounds>} */ |
| 52 boundsMap_: {}, |
| 53 |
| 54 /** @private {!{left: number, top: number}} */ |
| 55 visualOffset_: {left: 0, top: 0}, |
| 56 |
| 57 /** @override */ |
| 58 attached: function() { |
| 59 // TODO(stevenjb): Remove retry once fixed: |
| 60 // https://github.com/Polymer/polymer/issues/3629 |
| 61 var self = this; |
| 62 var retry = 100; // ms |
| 63 function tryCalcVisualScale() { |
| 64 if (!self.calculateVisualScale_()) |
| 65 setTimeout(tryCalcVisualScale, retry); |
| 66 } |
| 67 tryCalcVisualScale(); |
| 68 }, |
| 69 |
| 70 /** |
| 71 * Called explicitly when |this.displays| and their associated |this.layouts| |
| 72 * have been fetched from chrome. |
| 73 * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays |
| 74 * @param {!Array<!chrome.system.display.DisplayLayout>} layouts |
| 75 */ |
| 76 updateDisplays: function(displays, layouts) { |
| 77 this.displays = displays; |
| 78 this.layouts = layouts; |
| 79 |
| 80 this.displayMap_ = {}; |
| 81 for (let display of this.displays) |
| 82 this.displayMap_[display.id] = display; |
| 83 |
| 84 this.layoutMap_ = {}; |
| 85 for (let layout of this.layouts) |
| 86 this.layoutMap_[layout.id] = layout; |
| 87 |
| 88 this.boundsMap_ = {}; |
| 89 for (let display of this.displays) |
| 90 this.calcDisplayBounds_(display); |
| 91 |
| 92 this.calculateVisualScale_(); |
| 93 }, |
| 94 |
| 95 /** |
| 96 * Calculates the visual offset and scale for the display area |
| 97 * (i.e. the ratio of the display area div size to the area required to |
| 98 * contain the DisplayUnitInfo bounding boxes). |
| 99 * @return {boolean} Whether the calculation was successful. |
| 100 * @private |
| 101 */ |
| 102 calculateVisualScale_() { |
| 103 var displayAreaDiv = this.$.displayArea; |
| 104 if (!displayAreaDiv || !displayAreaDiv.offsetWidth || !this.displays || |
| 105 !this.displays.length) { |
| 106 return false; |
| 107 } |
| 108 |
| 109 var display = this.displays[0]; |
| 110 var bounds = this.boundsMap_[display.id]; |
| 111 var displayInfoBoundingBox = { |
| 112 left: bounds.left, |
| 113 right: bounds.left + bounds.width, |
| 114 top: bounds.top, |
| 115 bottom: bounds.top + bounds.height, |
| 116 }; |
| 117 var maxWidth = bounds.width; |
| 118 var maxHeight = bounds.height; |
| 119 for (let i = 1; i < this.displays.length; ++i) { |
| 120 display = this.displays[i]; |
| 121 bounds = this.boundsMap_[display.id]; |
| 122 displayInfoBoundingBox.left = |
| 123 Math.min(displayInfoBoundingBox.left, bounds.left); |
| 124 displayInfoBoundingBox.right = |
| 125 Math.max(displayInfoBoundingBox.right, bounds.left + bounds.width); |
| 126 displayInfoBoundingBox.top = |
| 127 Math.min(displayInfoBoundingBox.top, bounds.top); |
| 128 displayInfoBoundingBox.bottom = |
| 129 Math.max(displayInfoBoundingBox.bottom, bounds.top + bounds.height); |
| 130 maxWidth = Math.max(maxWidth, bounds.width); |
| 131 maxHeight = Math.max(maxHeight, bounds.height); |
| 132 } |
| 133 |
| 134 // Create a margin around the bounding box equal to the size of the |
| 135 // largest displays. |
| 136 var displayInfoBoundsWidth = displayInfoBoundingBox.right - |
| 137 displayInfoBoundingBox.left + maxWidth * 2; |
| 138 var displayInfoBoundsHeight = displayInfoBoundingBox.bottom - |
| 139 displayInfoBoundingBox.top + maxHeight * 2; |
| 140 |
| 141 // Calculate the scale. |
| 142 var horizontalScale = displayAreaDiv.offsetWidth / displayInfoBoundsWidth; |
| 143 var verticalScale = displayAreaDiv.offsetHeight / displayInfoBoundsHeight; |
| 144 var scale = Math.min(horizontalScale, verticalScale); |
| 145 |
| 146 // Calculate the offset. |
| 147 this.visualOffset_.left = (-displayInfoBoundingBox.left + maxWidth) * scale; |
| 148 this.visualOffset_.top = (-displayInfoBoundingBox.top + maxHeight) * scale; |
| 149 |
| 150 // Update the scale which will trigger calls to getDivStyle_. |
| 151 this.visualScale = Math.max(MIN_VISUAL_SCALE, scale); |
| 152 |
| 153 return true; |
| 154 }, |
| 155 |
| 156 /** |
| 157 * @param {!chrome.system.display.DisplayUnitInfo} display |
| 158 * @param {number} visualScale |
| 159 * @return {string} The style string for the div. |
| 160 * @private |
| 161 */ |
| 162 getDivStyle_: function(display, visualScale) { |
| 163 // This matches the size of the box-shadow or border in CSS. |
| 164 /** @const {number} */ var BORDER = 2; |
| 165 var bounds = this.boundsMap_[display.id]; |
| 166 var height = Math.round(bounds.height * this.visualScale) - BORDER * 2; |
| 167 var width = Math.round(bounds.width * this.visualScale) - BORDER * 2; |
| 168 var left = |
| 169 Math.round(this.visualOffset_.left + (bounds.left * this.visualScale)); |
| 170 var top = |
| 171 Math.round(this.visualOffset_.top + (bounds.top * this.visualScale)); |
| 172 return `height: ${height}px; width: ${width}px;` + |
| 173 ` left: ${left}px; top: ${top}px`; |
| 174 }, |
| 175 |
| 176 /** |
| 177 * @param {!chrome.system.display.DisplayUnitInfo} display |
| 178 * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
| 179 * @return {boolean} |
| 180 * @private |
| 181 */ |
| 182 isSelected_: function(display, selectedDisplay) { |
| 183 return display.id == selectedDisplay.id; |
| 184 }, |
| 185 |
| 186 /** |
| 187 * @param {!{model: !{index: number}}} e |
| 188 * @private |
| 189 */ |
| 190 onSelectDisplayTap_: function(e) { |
| 191 this.fire('select-display', e.model.index); |
| 192 }, |
| 193 |
| 194 /** |
| 195 * Recursively calculate the bounds of a display relative to its parents. |
| 196 * Caches the display bounds so that parent bounds are only calculated once. |
| 197 * TODO(stevenjb): Move this function and the maps it requires to a separate |
| 198 * behavior which will include snapping and collisions. |
| 199 * @param {!chrome.system.display.DisplayUnitInfo} display |
| 200 * @private |
| 201 */ |
| 202 calcDisplayBounds_: function(display) { |
| 203 if (display.id in this.boundsMap_) |
| 204 return; // Already calculated (i.e. a parent of a previous display) |
| 205 var left, top; |
| 206 if (display.isPrimary) { |
| 207 left = -display.bounds.width / 2; |
| 208 top = -display.bounds.height / 2; |
| 209 } else { |
| 210 var layout = this.layoutMap_[display.id]; |
| 211 assert(layout.parentId); |
| 212 var parentDisplay = this.displayMap_[layout.parentId]; |
| 213 var parentBounds; |
| 214 if (!(parentDisplay.id in this.boundsMap_)) |
| 215 this.calcDisplayBounds_(parentDisplay); |
| 216 parentBounds = this.boundsMap_[parentDisplay.id]; |
| 217 left = parentBounds.left; |
| 218 top = parentBounds.top; |
| 219 switch (layout.position) { |
| 220 case chrome.system.display.LayoutPosition.TOP: |
| 221 top -= display.bounds.height; |
| 222 break; |
| 223 case chrome.system.display.LayoutPosition.RIGHT: |
| 224 left += parentBounds.width; |
| 225 break; |
| 226 case chrome.system.display.LayoutPosition.BOTTOM: |
| 227 top += parentBounds.height; |
| 228 break; |
| 229 case chrome.system.display.LayoutPosition.LEFT: |
| 230 left -= display.bounds.height; |
| 231 break; |
| 232 } |
| 233 } |
| 234 var result = { |
| 235 left: left, |
| 236 top: top, |
| 237 width: display.bounds.width, |
| 238 height: display.bounds.height |
| 239 }; |
| 240 this.boundsMap_[display.id] = result; |
| 241 } |
| 242 }); |
| 243 |
| 244 })(); |
OLD | NEW |