OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * 'display-layout' presents a visual representation of the layout of one or | 7 * 'display-layout' presents a visual representation of the layout of one or |
8 * more displays and allows them to be arranged. | 8 * more displays and allows them to be arranged. |
9 */ | 9 */ |
10 | 10 |
11 (function() { | 11 (function() { |
12 | 12 |
13 /** @const {number} */ var MIN_VISUAL_SCALE = .01; | 13 /** @const {number} */ var MIN_VISUAL_SCALE = .01; |
14 | 14 |
15 Polymer({ | 15 Polymer({ |
16 is: 'display-layout', | 16 is: 'display-layout', |
17 | 17 |
18 behaviors: [ | 18 behaviors: [ |
19 Polymer.IronResizableBehavior, | 19 Polymer.IronResizableBehavior, |
20 DragBehavior, | 20 DragBehavior, |
21 LayoutBehavior, | |
21 ], | 22 ], |
22 | 23 |
23 properties: { | 24 properties: { |
24 /** | 25 /** |
25 * Array of displays. | 26 * Array of displays. |
26 * @type {!Array<!chrome.system.display.DisplayUnitInfo>} | 27 * @type {!Array<!chrome.system.display.DisplayUnitInfo>} |
27 */ | 28 */ |
28 displays: Array, | 29 displays: Array, |
29 | 30 |
30 /** | 31 /** |
31 * Array of display layouts. | |
32 * @type {!Array<!chrome.system.display.DisplayLayout>} | |
33 */ | |
34 layouts: Array, | |
35 | |
36 /** | |
37 * Whether or not mirroring is enabled. | 32 * Whether or not mirroring is enabled. |
38 * @type {boolean} | 33 * @type {boolean} |
39 */ | 34 */ |
40 mirroring: false, | 35 mirroring: false, |
41 | 36 |
42 /** @type {!chrome.system.display.DisplayUnitInfo|undefined} */ | 37 /** @type {!chrome.system.display.DisplayUnitInfo|undefined} */ |
43 selectedDisplay: Object, | 38 selectedDisplay: Object, |
44 | 39 |
45 /** | 40 /** |
46 * The ratio of the display area div (in px) to DisplayUnitInfo.bounds. | 41 * The ratio of the display area div (in px) to DisplayUnitInfo.bounds. |
47 * @type {number} | 42 * @type {number} |
48 */ | 43 */ |
49 visualScale: 1, | 44 visualScale: 1, |
50 }, | 45 }, |
51 | 46 |
52 /** @private {!Object<chrome.system.display.Bounds>} */ | 47 /** @type {!{left: number, top: number}} */ |
michaelpg
2016/06/27 22:09:32
still @private, right?
stevenjb
2016/06/27 23:25:46
Yeah, Merge. Done.
| |
53 displayBoundsMap_: {}, | |
54 | |
55 /** @private {!Object<chrome.system.display.DisplayLayout>} */ | |
56 layoutMap_: {}, | |
57 | |
58 /** | |
59 * The calculated bounds used for generating the div bounds. | |
60 * @private {!Object<chrome.system.display.Bounds>} | |
61 */ | |
62 calculatedBoundsMap_: {}, | |
63 | |
64 /** @private {!{left: number, top: number}} */ | |
65 visualOffset_: {left: 0, top: 0}, | 48 visualOffset_: {left: 0, top: 0}, |
66 | 49 |
67 /** @override */ | 50 /** @override */ |
68 attached: function() { | 51 attached: function() { |
69 // TODO(stevenjb): Remove retry once fixed: | 52 // TODO(stevenjb): Remove retry once fixed: |
70 // https://github.com/Polymer/polymer/issues/3629 | 53 // https://github.com/Polymer/polymer/issues/3629 |
71 var self = this; | 54 var self = this; |
72 var retry = 100; // ms | 55 var retry = 100; // ms |
73 function tryCalcVisualScale() { | 56 function tryCalcVisualScale() { |
74 if (!self.calculateVisualScale_()) | 57 if (!self.calculateVisualScale_()) |
75 setTimeout(tryCalcVisualScale, retry); | 58 setTimeout(tryCalcVisualScale, retry); |
76 } | 59 } |
77 tryCalcVisualScale(); | 60 tryCalcVisualScale(); |
78 }, | 61 }, |
79 | 62 |
80 /** @override */ | 63 /** @override */ |
81 detached: function() { | 64 detached: function() { this.initializeDrag(false); }, |
82 this.initializeDrag(false); | |
83 }, | |
84 | 65 |
85 /** | 66 /** |
86 * Called explicitly when |this.displays| and their associated |this.layouts| | 67 * Called explicitly when |this.displays| and their associated |this.layouts| |
87 * have been fetched from chrome. | 68 * have been fetched from chrome. |
88 * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays | 69 * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays |
89 * @param {!Array<!chrome.system.display.DisplayLayout>} layouts | 70 * @param {!Array<!chrome.system.display.DisplayLayout>} layouts |
90 */ | 71 */ |
91 updateDisplays: function(displays, layouts) { | 72 updateDisplays: function(displays, layouts) { |
92 this.displays = displays; | 73 this.displays = displays; |
93 this.layouts = layouts; | 74 this.layouts = layouts; |
94 | 75 |
95 this.mirroring = displays.length > 0 && !!displays[0].mirroringSourceId; | 76 this.mirroring = displays.length > 0 && !!displays[0].mirroringSourceId; |
96 | 77 |
97 this.displayBoundsMap_ = {}; | 78 this.initializeDisplayLayout(displays, layouts); |
98 for (let display of this.displays) | |
99 this.displayBoundsMap_[display.id] = display.bounds; | |
100 | |
101 this.layoutMap_ = {}; | |
102 for (let layout of this.layouts) | |
103 this.layoutMap_[layout.id] = layout; | |
104 | |
105 this.calculatedBoundsMap_ = {}; | |
106 for (let display of this.displays) | |
107 this.calculateBounds_(display.id, display.bounds); | |
108 | 79 |
109 this.calculateVisualScale_(); | 80 this.calculateVisualScale_(); |
110 | 81 |
111 this.initializeDrag( | 82 this.initializeDrag( |
112 !this.mirroring, this.$.displayArea, this.onDrag_.bind(this)); | 83 !this.mirroring, this.$.displayArea, this.onDrag_.bind(this)); |
113 }, | 84 }, |
114 | 85 |
115 /** | 86 /** |
116 * Calculates the visual offset and scale for the display area | 87 * Calculates the visual offset and scale for the display area |
117 * (i.e. the ratio of the display area div size to the area required to | 88 * (i.e. the ratio of the display area div size to the area required to |
118 * contain the DisplayUnitInfo bounding boxes). | 89 * contain the DisplayUnitInfo bounding boxes). |
119 * @return {boolean} Whether the calculation was successful. | 90 * @return {boolean} Whether the calculation was successful. |
120 * @private | 91 * @private |
121 */ | 92 */ |
122 calculateVisualScale_() { | 93 calculateVisualScale_() { |
123 var displayAreaDiv = this.$.displayArea; | 94 var displayAreaDiv = this.$.displayArea; |
124 if (!displayAreaDiv || !displayAreaDiv.offsetWidth || !this.displays || | 95 if (!displayAreaDiv || !displayAreaDiv.offsetWidth || !this.displays || |
125 !this.displays.length) { | 96 !this.displays.length) { |
126 return false; | 97 return false; |
127 } | 98 } |
128 | 99 |
129 var display = this.displays[0]; | 100 var display = this.displays[0]; |
130 var bounds = this.calculatedBoundsMap_[display.id]; | 101 var bounds = this.getCalculatedDisplayBounds(display.id); |
131 var displayInfoBoundingBox = { | 102 var boundsBoundingBox = { |
132 left: bounds.left, | 103 left: bounds.left, |
133 right: bounds.left + bounds.width, | 104 right: bounds.left + bounds.width, |
134 top: bounds.top, | 105 top: bounds.top, |
135 bottom: bounds.top + bounds.height, | 106 bottom: bounds.top + bounds.height, |
136 }; | 107 }; |
137 var maxWidth = bounds.width; | 108 var maxWidth = bounds.width; |
138 var maxHeight = bounds.height; | 109 var maxHeight = bounds.height; |
139 for (let i = 1; i < this.displays.length; ++i) { | 110 for (let i = 1; i < this.displays.length; ++i) { |
140 display = this.displays[i]; | 111 display = this.displays[i]; |
141 bounds = this.calculatedBoundsMap_[display.id]; | 112 bounds = this.getCalculatedDisplayBounds(display.id); |
142 displayInfoBoundingBox.left = | 113 boundsBoundingBox.left = Math.min(boundsBoundingBox.left, bounds.left); |
143 Math.min(displayInfoBoundingBox.left, bounds.left); | 114 boundsBoundingBox.right = |
144 displayInfoBoundingBox.right = | 115 Math.max(boundsBoundingBox.right, bounds.left + bounds.width); |
145 Math.max(displayInfoBoundingBox.right, bounds.left + bounds.width); | 116 boundsBoundingBox.top = Math.min(boundsBoundingBox.top, bounds.top); |
146 displayInfoBoundingBox.top = | 117 boundsBoundingBox.bottom = |
147 Math.min(displayInfoBoundingBox.top, bounds.top); | 118 Math.max(boundsBoundingBox.bottom, bounds.top + bounds.height); |
148 displayInfoBoundingBox.bottom = | |
149 Math.max(displayInfoBoundingBox.bottom, bounds.top + bounds.height); | |
150 maxWidth = Math.max(maxWidth, bounds.width); | 119 maxWidth = Math.max(maxWidth, bounds.width); |
151 maxHeight = Math.max(maxHeight, bounds.height); | 120 maxHeight = Math.max(maxHeight, bounds.height); |
152 } | 121 } |
153 | 122 |
154 // Create a margin around the bounding box equal to the size of the | 123 // Create a margin around the bounding box equal to the size of the |
155 // largest displays. | 124 // largest displays. |
156 var displayInfoBoundsWidth = displayInfoBoundingBox.right - | 125 var boundsWidth = boundsBoundingBox.right - boundsBoundingBox.left; |
157 displayInfoBoundingBox.left + maxWidth * 2; | 126 var boundsHeight = boundsBoundingBox.bottom - boundsBoundingBox.top; |
158 var displayInfoBoundsHeight = displayInfoBoundingBox.bottom - | |
159 displayInfoBoundingBox.top + maxHeight * 2; | |
160 | 127 |
161 // Calculate the scale. | 128 // Calculate the scale. |
162 var horizontalScale = displayAreaDiv.offsetWidth / displayInfoBoundsWidth; | 129 var horizontalScale = |
163 var verticalScale = displayAreaDiv.offsetHeight / displayInfoBoundsHeight; | 130 displayAreaDiv.offsetWidth / (boundsWidth + maxWidth * 2); |
131 var verticalScale = | |
132 displayAreaDiv.offsetHeight / (boundsHeight + maxHeight * 2); | |
164 var scale = Math.min(horizontalScale, verticalScale); | 133 var scale = Math.min(horizontalScale, verticalScale); |
165 | 134 |
166 // Calculate the offset. | 135 // Calculate the offset. |
167 this.visualOffset_.left = (-displayInfoBoundingBox.left + maxWidth) * scale; | 136 this.visualOffset_.left = |
168 this.visualOffset_.top = (-displayInfoBoundingBox.top + maxHeight) * scale; | 137 ((displayAreaDiv.offsetWidth - (boundsWidth * scale)) / 2) - |
138 boundsBoundingBox.left * scale; | |
139 this.visualOffset_.top = | |
140 ((displayAreaDiv.offsetHeight - (boundsHeight * scale)) / 2) - | |
141 boundsBoundingBox.top * scale; | |
169 | 142 |
170 // Update the scale which will trigger calls to getDivStyle_. | 143 // Update the scale which will trigger calls to getDivStyle_. |
171 this.visualScale = Math.max(MIN_VISUAL_SCALE, scale); | 144 this.visualScale = Math.max(MIN_VISUAL_SCALE, scale); |
172 | 145 |
173 return true; | 146 return true; |
174 }, | 147 }, |
175 | 148 |
176 /** | 149 /** |
177 * @param {string} id | 150 * @param {string} id |
178 * @param {!chrome.system.display.Bounds} displayBounds | 151 * @param {!chrome.system.display.Bounds} displayBounds |
179 * @param {number} visualScale | 152 * @param {number} visualScale |
180 * @return {string} The style string for the div. | 153 * @return {string} The style string for the div. |
181 * @private | 154 * @private |
182 */ | 155 */ |
183 getDivStyle_: function(id, displayBounds, visualScale) { | 156 getDivStyle_: function(id, displayBounds, visualScale) { |
184 // This matches the size of the box-shadow or border in CSS. | 157 // This matches the size of the box-shadow or border in CSS. |
185 /** @const {number} */ var BORDER = 2; | 158 /** @const {number} */ var BORDER = 2; |
186 var bounds = this.calculatedBoundsMap_[id]; | 159 var bounds = this.getCalculatedDisplayBounds(id); |
187 var height = Math.round(bounds.height * this.visualScale) - BORDER * 2; | 160 var height = Math.round(bounds.height * this.visualScale) - BORDER * 2; |
188 var width = Math.round(bounds.width * this.visualScale) - BORDER * 2; | 161 var width = Math.round(bounds.width * this.visualScale) - BORDER * 2; |
189 var left = | 162 var left = |
190 Math.round(this.visualOffset_.left + (bounds.left * this.visualScale)); | 163 Math.round(this.visualOffset_.left + (bounds.left * this.visualScale)); |
191 var top = | 164 var top = |
192 Math.round(this.visualOffset_.top + (bounds.top * this.visualScale)); | 165 Math.round(this.visualOffset_.top + (bounds.top * this.visualScale)); |
193 return `height: ${height}px; width: ${width}px;` + | 166 return `height: ${height}px; width: ${width}px;` + |
194 ` left: ${left}px; top: ${top}px`; | 167 ` left: ${left}px; top: ${top}px`; |
195 }, | 168 }, |
196 | 169 |
197 /** | 170 /** |
198 * @param {!chrome.system.display.DisplayUnitInfo} display | 171 * @param {!chrome.system.display.DisplayUnitInfo} display |
199 * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay | 172 * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay |
200 * @return {boolean} | 173 * @return {boolean} |
201 * @private | 174 * @private |
202 */ | 175 */ |
203 isSelected_: function(display, selectedDisplay) { | 176 isSelected_: function(display, selectedDisplay) { |
204 return display.id == selectedDisplay.id; | 177 return display.id == selectedDisplay.id; |
205 }, | 178 }, |
206 | 179 |
207 /** | 180 /** |
208 * @param {!{model: !{index: number}}} e | 181 * @param {!{model: !{item: !chrome.system.display.DisplayUnitInfo}, |
182 * target: !PaperButtonElement}} e | |
209 * @private | 183 * @private |
210 */ | 184 */ |
211 onSelectDisplayTap_: function(e) { | 185 onSelectDisplayTap_: function(e) { |
212 this.fire('select-display', e.model.index); | 186 this.fire('select-display', e.model.item.id); |
187 // Force active in case the selected display was clicked. | |
188 e.target.active = true; | |
213 }, | 189 }, |
214 | 190 |
215 /** | 191 /** |
216 * Recursively calculate the bounds of a display relative to its parents. | |
217 * Caches the display bounds so that parent bounds are only calculated once. | |
218 * TODO(stevenjb): Move this function and the maps it requires to a separate | |
219 * behavior which will include snapping and collisions. | |
220 * @param {string} id | |
221 * @param {!chrome.system.display.Bounds} bounds | |
222 * @private | |
223 */ | |
224 calculateBounds_: function(id, bounds) { | |
225 if (id in this.calculatedBoundsMap_) | |
226 return; // Already calculated (i.e. a parent of a previous display) | |
227 var left, top; | |
228 var layout = this.layoutMap_[id]; | |
229 if (!layout || !layout.parentId) { | |
230 left = -bounds.width / 2; | |
231 top = -bounds.height / 2; | |
232 } else { | |
233 var parentDisplayBounds = this.displayBoundsMap_[layout.parentId]; | |
234 var parentBounds; | |
235 if (!(layout.parentId in this.calculatedBoundsMap_)) | |
236 this.calculateBounds_(layout.parentId, parentDisplayBounds); | |
237 parentBounds = this.calculatedBoundsMap_[layout.parentId]; | |
238 left = parentBounds.left; | |
239 top = parentBounds.top; | |
240 switch (layout.position) { | |
241 case chrome.system.display.LayoutPosition.TOP: | |
242 top -= bounds.height; | |
243 break; | |
244 case chrome.system.display.LayoutPosition.RIGHT: | |
245 left += parentBounds.width; | |
246 break; | |
247 case chrome.system.display.LayoutPosition.BOTTOM: | |
248 top += parentBounds.height; | |
249 break; | |
250 case chrome.system.display.LayoutPosition.LEFT: | |
251 left -= bounds.height; | |
252 break; | |
253 } | |
254 } | |
255 var result = { | |
256 left: left, | |
257 top: top, | |
258 width: bounds.width, | |
259 height: bounds.height | |
260 }; | |
261 this.calculatedBoundsMap_[id] = result; | |
262 }, | |
263 | |
264 /** | |
265 * @param {string} id | 192 * @param {string} id |
266 * @param {?DragPosition} amount | 193 * @param {?DragPosition} amount |
267 */ | 194 */ |
268 onDrag_(id, amount) { | 195 onDrag_(id, amount) { |
269 id = id.substr(1); // Skip prefix | 196 id = id.substr(1); // Skip prefix |
270 | 197 |
271 var newBounds; | 198 var newBounds; |
272 if (!amount) { | 199 if (!amount) { |
273 // TODO(stevenjb): Resolve layout and send update. | 200 this.finishUpdateDisplayBounds(id); |
274 newBounds = this.calculatedBoundsMap_[id]; | 201 newBounds = this.getCalculatedDisplayBounds(id); |
275 } else { | 202 } else { |
276 // Make sure the dragged display is also selected. | 203 // Make sure the dragged display is also selected. |
277 if (id != this.selectedDisplay.id) | 204 if (id != this.selectedDisplay.id) |
278 this.fire('select-display', id); | 205 this.fire('select-display', id); |
279 | 206 |
280 var calculatedBounds = this.calculatedBoundsMap_[id]; | 207 var calculatedBounds = this.getCalculatedDisplayBounds(id); |
281 newBounds = | 208 newBounds = |
282 /** @type {chrome.system.display.Bounds} */ ( | 209 /** @type {chrome.system.display.Bounds} */ ( |
283 Object.assign({}, calculatedBounds)); | 210 Object.assign({}, calculatedBounds)); |
284 newBounds.left += Math.round(amount.x / this.visualScale); | 211 newBounds.left += Math.round(amount.x / this.visualScale); |
285 newBounds.top += Math.round(amount.y / this.visualScale); | 212 newBounds.top += Math.round(amount.y / this.visualScale); |
286 // TODO(stevenjb): Update layout. | 213 newBounds = this.updateDisplayBounds(id, newBounds); |
287 } | 214 } |
288 var left = | 215 var left = |
289 this.visualOffset_.left + Math.round(newBounds.left * this.visualScale); | 216 this.visualOffset_.left + Math.round(newBounds.left * this.visualScale); |
290 var top = | 217 var top = |
291 this.visualOffset_.top + Math.round(newBounds.top * this.visualScale); | 218 this.visualOffset_.top + Math.round(newBounds.top * this.visualScale); |
292 var div = this.$$('#_' + id); | 219 var div = this.$$('#_' + id); |
293 div.style.left = '' + left + 'px'; | 220 div.style.left = '' + left + 'px'; |
294 div.style.top = '' + top + 'px'; | 221 div.style.top = '' + top + 'px'; |
295 }, | 222 }, |
296 | 223 |
297 }); | 224 }); |
298 | 225 |
299 })(); | 226 })(); |
OLD | NEW |