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 return false; | |
106 | |
107 var maxWidth = 0; | |
108 var maxHeight = 0; | |
109 var displayInfoBoundingBox = | |
110 {left: Number.MAX_VALUE, right: 0, top: Number.MAX_VALUE, bottom: 0}; | |
111 | |
112 for (let display of this.displays) { | |
113 var bounds = this.boundsMap_[display.id]; | |
114 displayInfoBoundingBox.left = | |
115 Math.min(displayInfoBoundingBox.left, bounds.left); | |
116 displayInfoBoundingBox.right = | |
117 Math.max(displayInfoBoundingBox.right, bounds.left + bounds.width); | |
118 displayInfoBoundingBox.top = | |
119 Math.min(displayInfoBoundingBox.top, bounds.top); | |
120 displayInfoBoundingBox.bottom = | |
121 Math.max(displayInfoBoundingBox.bottom, bounds.top + bounds.height); | |
122 maxWidth = Math.max(maxWidth, bounds.width); | |
123 maxHeight = Math.max(maxHeight, bounds.height); | |
124 } | |
125 | |
126 // Create a margin around the bounding box equal to the size of the | |
127 // largest display. | |
128 var displayInfoBoundsWidth = | |
129 displayInfoBoundingBox.right - displayInfoBoundingBox.left + | |
130 maxWidth * 2; | |
131 var displayInfoBoundsHeight = | |
132 displayInfoBoundingBox.bottom - displayInfoBoundingBox.top + | |
133 maxHeight * 2; | |
134 | |
135 // Calculate the scale. | |
136 var horizontalScale = displayAreaDiv.offsetWidth / displayInfoBoundsWidth; | |
137 var verticalScale = displayAreaDiv.offsetHeight / displayInfoBoundsHeight; | |
138 var scale = Math.min(horizontalScale, verticalScale); | |
139 | |
140 // Calculate the offset. | |
141 this.visualOffset_.left = (-displayInfoBoundingBox.left + maxWidth) * scale; | |
142 this.visualOffset_.top = (-displayInfoBoundingBox.top + maxHeight) * scale; | |
143 | |
144 // Update the scale which will trigger calls to getDivStyle_. | |
145 this.visualScale = Math.max(MIN_VISUAL_SCALE, scale); | |
146 | |
147 return true; | |
148 }, | |
149 | |
150 /** | |
151 * @param {!chrome.system.display.DisplayUnitInfo} display | |
152 * @param {number} visualScale | |
153 * @return {string} The style string for the div. | |
154 * @private | |
155 */ | |
156 getDivStyle_: function(display, visualScale) { | |
157 /** @const {number} */ var BORDER = 2; | |
158 var bounds = this.boundsMap_[display.id]; | |
159 var height = Math.round(bounds.height * this.visualScale) - BORDER * 2; | |
160 var width = Math.round(bounds.width * this.visualScale) - BORDER * 2; | |
161 var left = | |
162 this.visualOffset_.left + Math.round(bounds.left * this.visualScale); | |
163 var top = | |
164 this.visualOffset_.top + Math.round(bounds.top * this.visualScale); | |
165 var style = 'height:' + height + 'px;' + 'width:' + width + 'px;' + | |
166 'left:' + left + 'px;' + 'top:' + top + 'px;'; | |
167 return style; | |
168 }, | |
169 | |
170 /** | |
171 * @param {!chrome.system.display.DisplayUnitInfo} display | |
172 * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay | |
173 * @return {boolean} | |
174 * @private | |
175 */ | |
176 isSelected_: function(display, selectedDisplay) { | |
177 return display.id == selectedDisplay.id; | |
178 }, | |
179 | |
180 /** | |
181 * @param {!{model: !{index: number}, target: !PaperButtonElement}} e | |
182 * @private | |
183 */ | |
184 onSelectDisplayTap_: function(e) { | |
185 this.fire('select-display', e.model.index); | |
186 }, | |
187 | |
188 /** | |
189 * Recursively calculate the bounds of a display relative to its parents. | |
190 * Caches the display bounds so that parent bounds are only calculated once. | |
191 * TODO(stevenjb): Move this function and the maps it requires to a separate | |
192 * behavior which will include snapping and collisions. | |
193 * @param {!chrome.system.display.DisplayUnitInfo} display | |
194 * @return {!chrome.system.display.Bounds} | |
195 * @private | |
196 */ | |
197 calcDisplayBounds_: function(display) { | |
198 var left, top; | |
199 if (display.isPrimary) { | |
200 left = -display.bounds.width / 2; | |
201 top = -display.bounds.height / 2; | |
202 } else { | |
203 var layout = this.layoutMap_[display.id]; | |
204 var parentDisplay = this.displayMap_[layout.parentId]; | |
michaelpg
2016/06/23 04:29:35
layout.parentId can be '', making parentDisplay un
stevenjb
2016/06/23 19:11:01
The primary display does have to be the root, and
| |
205 var parentBounds; | |
206 if (parentDisplay.id in this.boundsMap_) | |
michaelpg
2016/06/23 04:29:35
it's a little weird that calcDisplayBounds_ both r
stevenjb
2016/06/23 19:11:01
Meh. Done.
| |
207 parentBounds = this.boundsMap_[parentDisplay.id]; | |
208 else | |
209 parentBounds = this.calcDisplayBounds_(parentDisplay); | |
michaelpg
2016/06/23 04:29:35
Does this mean calcDisplayBounds_ could be called
stevenjb
2016/06/23 19:11:01
Err, yes. I swear I checked the cache at the top o
| |
210 left = parentBounds.left; | |
211 top = parentBounds.top; | |
212 switch (layout.position) { | |
213 case chrome.system.display.LayoutPosition.TOP: | |
214 top -= display.bounds.height; | |
215 break; | |
216 case chrome.system.display.LayoutPosition.RIGHT: | |
217 left += parentBounds.width; | |
218 break; | |
219 case chrome.system.display.LayoutPosition.BOTTOM: | |
220 top += parentBounds.height; | |
221 break; | |
222 case chrome.system.display.LayoutPosition.LEFT: | |
223 left -= display.bounds.height; | |
224 break; | |
225 } | |
226 } | |
227 var result = { | |
228 left: left, | |
229 top: top, | |
230 width: display.bounds.width, | |
231 height: display.bounds.height | |
232 }; | |
233 this.boundsMap_[display.id] = result; | |
234 return result; | |
235 } | |
236 }); | |
237 | |
238 })(); | |
OLD | NEW |