Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(228)

Side by Side Diff: chrome/browser/resources/settings/device_page/display_layout.js

Issue 2097763004: MD Settings: Display: Add snapping and collisions (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@issue_547080_display_settings8a_drag
Patch Set: . Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698