OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
stevenjb
2016/01/26 01:31:26
Note: This is a new file diffing from display_opti
| |
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 cr.exportPath('options'); | 5 cr.exportPath('options'); |
6 | 6 |
7 /** | 7 /** |
8 * Enumeration of display layout. These values must match the C++ values in | 8 * Enumeration of display layout. These values must match the C++ values in |
9 * ash::DisplayController. | 9 * ash::DisplayController. |
10 * @enum {number} | 10 * @enum {number} |
11 */ | 11 */ |
12 options.DisplayLayoutType = { | 12 options.DisplayLayoutType = { |
13 TOP: 0, | 13 TOP: 0, |
14 RIGHT: 1, | 14 RIGHT: 1, |
15 BOTTOM: 2, | 15 BOTTOM: 2, |
16 LEFT: 3 | 16 LEFT: 3 |
17 }; | 17 }; |
18 | 18 |
19 /** | 19 /** |
20 * Enumeration of multi display mode. These values must match the C++ values in | |
21 * ash::DisplayManager. | |
22 * @enum {number} | |
23 */ | |
24 options.MultiDisplayMode = { | |
25 EXTENDED: 0, | |
26 MIRRORING: 1, | |
27 UNIFIED: 2, | |
28 }; | |
29 | |
30 /** | |
31 * @typedef {{ | 20 * @typedef {{ |
32 * left: number, | 21 * left: number, |
33 * top: number, | 22 * top: number, |
34 * width: number, | 23 * width: number, |
35 * height: number | 24 * height: number |
36 * }} | 25 * }} |
37 */ | 26 */ |
38 options.DisplayBounds; | 27 options.DisplayBounds; |
39 | 28 |
40 /** | 29 /** |
41 * @typedef {{ | 30 * @typedef {{ |
42 * x: number, | 31 * x: number, |
43 * y: number | 32 * y: number |
44 * }} | 33 * }} |
45 */ | 34 */ |
46 options.DisplayPosition; | 35 options.DisplayPosition; |
47 | 36 |
48 /** | 37 /** |
49 * @typedef {{ | 38 * @typedef {{ |
50 * width: number, | |
51 * height: number, | |
52 * originalWidth: number, | |
53 * originalHeight: number, | |
54 * deviceScaleFactor: number, | |
55 * scale: number, | |
56 * refreshRate: number, | |
57 * isBest: boolean, | |
58 * selected: boolean | |
59 * }} | |
60 */ | |
61 options.DisplayMode; | |
62 | |
63 /** | |
64 * @typedef {{ | |
65 * profileId: number, | |
66 * name: string | |
67 * }} | |
68 */ | |
69 options.ColorProfile; | |
70 | |
71 /** | |
72 * @typedef {{ | |
73 * availableColorProfiles: !Array<!options.ColorProfile>, | |
74 * bounds: !options.DisplayBounds, | |
75 * colorProfileId: number, | |
76 * id: string, | |
77 * isInternal: boolean, | |
78 * isPrimary: boolean, | |
79 * resolutions: !Array<!options.DisplayMode>, | |
80 * name: string, | |
81 * rotation: number | |
82 * }} | |
83 */ | |
84 options.DisplayInfo; | |
85 | |
86 /** | |
87 * @typedef {{ | |
88 * bounds: !options.DisplayBounds, | 39 * bounds: !options.DisplayBounds, |
89 * div: ?HTMLElement, | 40 * div: ?HTMLElement, |
90 * id: string, | 41 * id: string, |
91 * isPrimary: boolean, | |
92 * layoutType: options.DisplayLayoutType, | 42 * layoutType: options.DisplayLayoutType, |
93 * name: string, | 43 * name: string, |
94 * originalPosition: !options.DisplayPosition | 44 * offset: number, |
45 * originalPosition: !options.DisplayPosition, | |
46 * parentId: string | |
95 * }} | 47 * }} |
96 */ | 48 */ |
97 options.DisplayLayout; | 49 options.DisplayLayout; |
98 | 50 |
99 cr.define('options', function() { | 51 cr.define('options', function() { |
100 var Page = cr.ui.pageManager.Page; | 52 'use strict'; |
101 var PageManager = cr.ui.pageManager.PageManager; | |
102 | |
103 // The scale ratio of the display rectangle to its original size. | |
104 /** @const */ var VISUAL_SCALE = 1 / 10; | |
105 | |
106 // The number of pixels to share the edges between displays. | |
107 /** @const */ var MIN_OFFSET_OVERLAP = 5; | |
108 | 53 |
109 /** | 54 /** |
110 * Gets the layout type of |point| relative to |rect|. | 55 * Gets the layout type of |point| relative to |rect|. |
111 * @param {!options.DisplayBounds} rect The base rectangle. | 56 * @param {!options.DisplayBounds} rect The base rectangle. |
112 * @param {!options.DisplayPosition} point The point to check the position. | 57 * @param {!options.DisplayPosition} point The point to check the position. |
113 * @return {options.DisplayLayoutType} The position of the calculated point. | 58 * @return {options.DisplayLayoutType} The position of the calculated point. |
114 */ | 59 */ |
115 function getPositionToRectangle(rect, point) { | 60 function getPositionToRectangle(rect, point) { |
116 // Separates the area into four (LEFT/RIGHT/TOP/BOTTOM) by the diagonals of | 61 // Separates the area into four (LEFT/RIGHT/TOP/BOTTOM) by the diagonals of |
117 // the rect, and decides which area the display should reside. | 62 // the rect, and decides which area the display should reside. |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
153 // Prefer the closer one if both edges are close enough. | 98 // Prefer the closer one if both edges are close enough. |
154 if (startDiff < SNAP_DISTANCE_PX && startDiff < endDiff) | 99 if (startDiff < SNAP_DISTANCE_PX && startDiff < endDiff) |
155 return basePoint; | 100 return basePoint; |
156 else if (endDiff < SNAP_DISTANCE_PX) | 101 else if (endDiff < SNAP_DISTANCE_PX) |
157 return basePoint + baseWidth - width; | 102 return basePoint + baseWidth - width; |
158 | 103 |
159 return point; | 104 return point; |
160 } | 105 } |
161 | 106 |
162 /** | 107 /** |
163 * Encapsulated handling of the 'Display' page. | 108 * @param {number} visualScale |
164 * @constructor | 109 * @constructor |
165 * @extends {cr.ui.pageManager.Page} | |
166 */ | 110 */ |
167 function DisplayOptions() { | 111 function DisplayLayoutManager(visualScale) { |
168 Page.call(this, 'display', | 112 this.visualScale_ = visualScale; |
169 loadTimeData.getString('displayOptionsPageTabTitle'), | |
170 'display-options-page'); | |
171 } | 113 } |
172 | 114 |
173 cr.addSingletonGetter(DisplayOptions); | 115 // Helper class for display layout management. Implements logic for laying |
174 | 116 // out two displays. |
175 DisplayOptions.prototype = { | 117 DisplayLayoutManager.prototype = { |
176 __proto__: Page.prototype, | |
177 | |
178 /** | 118 /** |
179 * Whether the current output status is mirroring displays or not. | 119 * An object containing DisplayLayout objects for each entry in |
180 * @type {boolean} | 120 * |displays_|. |
181 * @private | |
182 */ | |
183 mirroring_: false, | |
184 | |
185 /** | |
186 * Whether the unified desktop is enable or not. | |
187 * @type {boolean} | |
188 * @private | |
189 */ | |
190 unifiedDesktopEnabled_: false, | |
191 | |
192 /** | |
193 * Whether the unified desktop option should be present. | |
194 * @type {boolean} | |
195 * @private | |
196 */ | |
197 showUnifiedDesktopOption_: false, | |
198 | |
199 /** | |
200 * The array of current output displays. It also contains the display | |
201 * rectangles currently rendered on screen. | |
202 * @type {!Array<!options.DisplayInfo>} | |
203 * @private | |
204 */ | |
205 displays_: [], | |
206 | |
207 /** | |
208 * An object containing DisplayLayout objects for each entry in |displays_|. | |
209 * @type {!Object<!options.DisplayLayout>} | 121 * @type {!Object<!options.DisplayLayout>} |
210 * @private | 122 * @private |
211 */ | 123 */ |
212 displayLayoutMap_: {}, | 124 displayLayoutMap_: {}, |
213 | 125 |
214 /** | 126 /** |
215 * The id of the currently focused display, or empty for none. | |
216 * @type {string} | |
217 * @private | |
218 */ | |
219 focusedId_: '', | |
220 | |
221 /** | |
222 * The primary display id. | |
223 * @type {string} | |
224 * @private | |
225 */ | |
226 primaryDisplayId_: '', | |
227 | |
228 /** | |
229 * The secondary display id. | |
230 * @type {string} | |
231 * @private | |
232 */ | |
233 secondaryDisplayId_: '', | |
234 | |
235 /** | |
236 * Drag info. | |
237 * @type {?{displayId: string, | |
238 * originalLocation: !options.DisplayPosition, | |
239 * eventLocation: !options.DisplayPosition}} | |
240 * @private | |
241 */ | |
242 dragInfo_: null, | |
243 | |
244 /** | |
245 * The container div element which contains all of the display rectangles. | |
246 * @type {?Element} | |
247 * @private | |
248 */ | |
249 displaysView_: null, | |
250 | |
251 /** | |
252 * The scale factor of the actual display size to the drawn display | 127 * The scale factor of the actual display size to the drawn display |
253 * rectangle size. | 128 * rectangle size. Set to the correct value for the UI in the constructor. |
254 * @type {number} | 129 * @type {number} |
255 * @private | 130 * @private |
256 */ | 131 */ |
257 visualScale_: VISUAL_SCALE, | 132 visualScale_: 1, |
258 | 133 |
259 /** | 134 /** |
260 * The location where the last touch event happened. This is used to | 135 * Adds a display to the layout map. |
261 * prevent unnecessary dragging events happen. Set to null unless it's | 136 * @param {options.DisplayLayout} displayLayout |
262 * during touch events. | |
263 * @type {?options.DisplayPosition} | |
264 * @private | |
265 */ | 137 */ |
266 lastTouchLocation_: null, | 138 addDisplayLayout: function(displayLayout) { |
267 | 139 this.displayLayoutMap_[displayLayout.id] = displayLayout; |
268 /** | |
269 * Whether the display settings can be shown. | |
270 * @type {boolean} | |
271 * @private | |
272 */ | |
273 enabled_: true, | |
274 | |
275 /** @override */ | |
276 initializePage: function() { | |
277 Page.prototype.initializePage.call(this); | |
278 | |
279 $('display-options-select-mirroring').onchange = (function() { | |
280 this.mirroring_ = | |
281 $('display-options-select-mirroring').value == 'mirroring'; | |
282 chrome.send('setMirroring', [this.mirroring_]); | |
283 }).bind(this); | |
284 | |
285 var container = $('display-options-displays-view-host'); | |
286 container.onmousemove = this.onMouseMove_.bind(this); | |
287 window.addEventListener('mouseup', this.endDragging_.bind(this), true); | |
288 container.ontouchmove = this.onTouchMove_.bind(this); | |
289 container.ontouchend = this.endDragging_.bind(this); | |
290 | |
291 $('display-options-set-primary').onclick = (function() { | |
292 chrome.send('setPrimary', [this.focusedId_]); | |
293 }).bind(this); | |
294 $('display-options-resolution-selection').onchange = (function(ev) { | |
295 var display = this.getDisplayInfoFromId(this.focusedId_); | |
296 var resolution = display.resolutions[ev.target.value]; | |
297 chrome.send('setDisplayMode', [this.focusedId_, resolution]); | |
298 }).bind(this); | |
299 $('display-options-orientation-selection').onchange = (function(ev) { | |
300 var rotation = parseInt(ev.target.value, 10); | |
301 chrome.send('setRotation', [this.focusedId_, rotation]); | |
302 }).bind(this); | |
303 $('display-options-color-profile-selection').onchange = (function(ev) { | |
304 chrome.send('setColorProfile', [this.focusedId_, ev.target.value]); | |
305 }).bind(this); | |
306 $('selected-display-start-calibrating-overscan').onclick = (function() { | |
307 // Passes the target display ID. Do not specify it through URL hash, | |
308 // we do not care back/forward. | |
309 var displayOverscan = options.DisplayOverscan.getInstance(); | |
310 displayOverscan.setDisplayId(this.focusedId_); | |
311 PageManager.showPageByName('displayOverscan'); | |
312 chrome.send('coreOptionsUserMetricsAction', | |
313 ['Options_DisplaySetOverscan']); | |
314 }).bind(this); | |
315 | |
316 $('display-options-done').onclick = function() { | |
317 PageManager.closeOverlay(); | |
318 }; | |
319 | |
320 $('display-options-toggle-unified-desktop').onclick = (function() { | |
321 this.unifiedDesktopEnabled_ = !this.unifiedDesktopEnabled_; | |
322 chrome.send('setUnifiedDesktopEnabled', | |
323 [this.unifiedDesktopEnabled_]); | |
324 }).bind(this); | |
325 }, | |
326 | |
327 /** @override */ | |
328 didShowPage: function() { | |
329 var optionTitles = document.getElementsByClassName( | |
330 'selected-display-option-title'); | |
331 var maxSize = 0; | |
332 for (var i = 0; i < optionTitles.length; i++) | |
333 maxSize = Math.max(maxSize, optionTitles[i].clientWidth); | |
334 for (var i = 0; i < optionTitles.length; i++) | |
335 optionTitles[i].style.width = maxSize + 'px'; | |
336 chrome.send('getDisplayInfo'); | |
337 }, | |
338 | |
339 /** @override */ | |
340 canShowPage: function() { return this.enabled_; }, | |
341 | |
342 /** | |
343 * Enables or disables the page. When disabled, the page will not be able to | |
344 * open, and will close if currently opened. | |
345 * @param {boolean} enabled Whether the page should be enabled. | |
346 * @param {boolean} showUnifiedDesktop Whether the unified desktop option | |
347 * should be present. | |
348 */ | |
349 setEnabled: function(enabled, showUnifiedDesktop) { | |
350 if (this.enabled_ == enabled && | |
351 this.showUnifiedDesktopOption_ == showUnifiedDesktop) { | |
352 return; | |
353 } | |
354 this.enabled_ = enabled; | |
355 this.showUnifiedDesktopOption_ = showUnifiedDesktop; | |
356 if (!enabled && this.visible) | |
357 PageManager.closeOverlay(); | |
358 }, | 140 }, |
359 | 141 |
360 /** | 142 /** |
361 * Mouse move handler for dragging display rectangle. | 143 * Returns the layout type for |id|. |
362 * @param {Event} e The mouse move event. | 144 * @param {string} id |
363 * @private | 145 * @return {options.DisplayLayout} |
364 */ | 146 */ |
365 onMouseMove_: function(e) { | 147 getDisplayLayout: function(id) { return this.displayLayoutMap_[id]; }, |
366 return this.processDragging_(e, {x: e.pageX, y: e.pageY}); | 148 |
149 /** | |
150 * Creates a div for each entry in displayLayoutMap_. | |
151 * @param {!Element} parentElement The parent element to contain the div. | |
152 * @param {!options.DisplayPosition} offset The offset to the center of | |
153 * the display area. | |
154 */ | |
155 createDisplayLayoutDivs: function(parentElement, offset) { | |
156 for (var id in this.displayLayoutMap_) { | |
157 var layout = this.displayLayoutMap_[id]; | |
158 if (layout.div) | |
159 continue; // May have already been created by its child. | |
160 this.createDisplayLayoutDiv_(id, parentElement, offset); | |
161 } | |
367 }, | 162 }, |
368 | 163 |
369 /** | 164 /** |
370 * Touch move handler for dragging display rectangle. | 165 * Update the location of display |id| to |newPosition|. |
371 * @param {Event} e The touch move event. | 166 * @param {string} id |
167 * @param {options.DisplayPosition} newPosition | |
372 * @private | 168 * @private |
373 */ | 169 */ |
374 onTouchMove_: function(e) { | 170 updatePosition: function(id, newPosition) { |
375 if (e.touches.length != 1) | 171 var displayLayout = this.displayLayoutMap_[id]; |
376 return true; | 172 var div = displayLayout.div; |
377 | 173 var baseLayout = this.getBaseLayout_(displayLayout); |
378 var touchLocation = {x: e.touches[0].pageX, y: e.touches[0].pageY}; | |
379 // Touch move events happen even if the touch location doesn't change, but | |
380 // it doesn't need to process the dragging. Since sometimes the touch | |
381 // position changes slightly even though the user doesn't think to move | |
382 // the finger, very small move is just ignored. | |
383 /** @const */ var IGNORABLE_TOUCH_MOVE_PX = 1; | |
384 var xDiff = Math.abs(touchLocation.x - this.lastTouchLocation_.x); | |
385 var yDiff = Math.abs(touchLocation.y - this.lastTouchLocation_.y); | |
386 if (xDiff <= IGNORABLE_TOUCH_MOVE_PX && | |
387 yDiff <= IGNORABLE_TOUCH_MOVE_PX) { | |
388 return true; | |
389 } | |
390 | |
391 this.lastTouchLocation_ = touchLocation; | |
392 return this.processDragging_(e, touchLocation); | |
393 }, | |
394 | |
395 /** | |
396 * Mouse down handler for dragging display rectangle. | |
397 * @param {Event} e The mouse down event. | |
398 * @private | |
399 */ | |
400 onMouseDown_: function(e) { | |
401 if (this.mirroring_) | |
402 return true; | |
403 | |
404 if (e.button != 0) | |
405 return true; | |
406 | |
407 e.preventDefault(); | |
408 var target = assertInstanceof(e.target, HTMLElement); | |
409 return this.startDragging_(target, {x: e.pageX, y: e.pageY}); | |
410 }, | |
411 | |
412 /** | |
413 * Touch start handler for dragging display rectangle. | |
414 * @param {Event} e The touch start event. | |
415 * @private | |
416 */ | |
417 onTouchStart_: function(e) { | |
418 if (this.mirroring_) | |
419 return true; | |
420 | |
421 if (e.touches.length != 1) | |
422 return false; | |
423 | |
424 e.preventDefault(); | |
425 var touch = e.touches[0]; | |
426 this.lastTouchLocation_ = {x: touch.pageX, y: touch.pageY}; | |
427 var target = assertInstanceof(e.target, HTMLElement); | |
428 return this.startDragging_(target, this.lastTouchLocation_); | |
429 }, | |
430 | |
431 /** | |
432 * @param {string} id | |
433 * @return {options.DisplayInfo} | |
434 */ | |
435 getDisplayInfoFromId(id) { | |
436 return this.displays_.find(function(display) { | |
437 return display.id == id; | |
438 }); | |
439 }, | |
440 | |
441 /** | |
442 * Collects the current data and sends it to Chrome. | |
443 * @private | |
444 */ | |
445 sendDragResult_: function() { | |
446 // Offset is calculated from top or left edge. | |
447 var primary = this.displayLayoutMap_[this.primaryDisplayId_]; | |
448 var secondary = this.displayLayoutMap_[this.secondaryDisplayId_]; | |
449 var layoutType = secondary.layoutType; | |
450 var offset; | |
451 if (layoutType == options.DisplayLayoutType.LEFT || | |
452 layoutType == options.DisplayLayoutType.RIGHT) { | |
453 offset = secondary.div.offsetTop - primary.div.offsetTop; | |
454 } else { | |
455 offset = secondary.div.offsetLeft - primary.div.offsetLeft; | |
456 } | |
457 offset = Math.floor(offset / this.visualScale_); | |
458 chrome.send('setDisplayLayout', [secondary.id, layoutType, offset]); | |
459 }, | |
460 | |
461 /** | |
462 * Processes the actual dragging of display rectangle. | |
463 * @param {Event} e The event which triggers this drag. | |
464 * @param {options.DisplayPosition} eventLocation The location where the | |
465 * event happens. | |
466 * @private | |
467 */ | |
468 processDragging_: function(e, eventLocation) { | |
469 if (!this.dragInfo_) | |
470 return true; | |
471 | |
472 e.preventDefault(); | |
473 | |
474 // Note that current code of moving display-rectangles doesn't work | |
475 // if there are >=3 displays. This is our assumption for M21. | |
476 // TODO(mukai): Fix the code to allow >=3 displays. | |
477 var dragInfo = this.dragInfo_; | |
478 /** @type {options.DisplayPosition} */ var newPosition = { | |
479 x: dragInfo.originalLocation.x + | |
480 (eventLocation.x - dragInfo.eventLocation.x), | |
481 y: dragInfo.originalLocation.y + | |
482 (eventLocation.y - dragInfo.eventLocation.y) | |
483 }; | |
484 | |
485 var dragLayout = this.displayLayoutMap_[dragInfo.displayId]; | |
486 var draggingDiv = dragLayout.div; | |
487 | |
488 var baseDisplayId = dragLayout.isPrimary ? this.secondaryDisplayId_ : | |
489 this.primaryDisplayId_; | |
490 var baseLayout = this.displayLayoutMap_[baseDisplayId]; | |
491 var baseDiv = baseLayout.div; | 174 var baseDiv = baseLayout.div; |
492 | 175 |
493 newPosition.x = snapToEdge( | 176 newPosition.x = snapToEdge( |
494 newPosition.x, draggingDiv.offsetWidth, baseDiv.offsetLeft, | 177 newPosition.x, div.offsetWidth, baseDiv.offsetLeft, |
495 baseDiv.offsetWidth); | 178 baseDiv.offsetWidth); |
496 newPosition.y = snapToEdge( | 179 newPosition.y = snapToEdge( |
497 newPosition.y, draggingDiv.offsetHeight, baseDiv.offsetTop, | 180 newPosition.y, div.offsetHeight, baseDiv.offsetTop, |
498 baseDiv.offsetHeight); | 181 baseDiv.offsetHeight); |
499 | 182 |
500 /** @type {!options.DisplayPosition} */ var newCenter = { | 183 /** @type {!options.DisplayPosition} */ var newCenter = { |
501 x: newPosition.x + draggingDiv.offsetWidth / 2, | 184 x: newPosition.x + div.offsetWidth / 2, |
502 y: newPosition.y + draggingDiv.offsetHeight / 2 | 185 y: newPosition.y + div.offsetHeight / 2 |
503 }; | 186 }; |
504 | 187 |
505 /** @type {!options.DisplayBounds} */ var baseBounds = { | 188 /** @type {!options.DisplayBounds} */ var baseBounds = { |
506 left: baseDiv.offsetLeft, | 189 left: baseDiv.offsetLeft, |
507 top: baseDiv.offsetTop, | 190 top: baseDiv.offsetTop, |
508 width: baseDiv.offsetWidth, | 191 width: baseDiv.offsetWidth, |
509 height: baseDiv.offsetHeight | 192 height: baseDiv.offsetHeight |
510 }; | 193 }; |
511 | 194 |
512 var isPrimary = dragLayout.isPrimary; | 195 // This implementation considers only the case of two displays, i.e |
513 // layoutType is always stored in the secondary layout. | 196 // a single parent with one child. |
197 var isPrimary = displayLayout.parentId == ''; | |
198 | |
199 // layoutType is always stored in the child layout. | |
514 var layoutType = | 200 var layoutType = |
515 isPrimary ? baseLayout.layoutType : dragLayout.layoutType; | 201 isPrimary ? baseLayout.layoutType : displayLayout.layoutType; |
516 | 202 |
517 switch (getPositionToRectangle(baseBounds, newCenter)) { | 203 switch (getPositionToRectangle(baseBounds, newCenter)) { |
518 case options.DisplayLayoutType.RIGHT: | 204 case options.DisplayLayoutType.RIGHT: |
519 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : | 205 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : |
520 options.DisplayLayoutType.RIGHT; | 206 options.DisplayLayoutType.RIGHT; |
521 break; | 207 break; |
522 case options.DisplayLayoutType.LEFT: | 208 case options.DisplayLayoutType.LEFT: |
523 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : | 209 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : |
524 options.DisplayLayoutType.LEFT; | 210 options.DisplayLayoutType.LEFT; |
525 break; | 211 break; |
526 case options.DisplayLayoutType.TOP: | 212 case options.DisplayLayoutType.TOP: |
527 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : | 213 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : |
528 options.DisplayLayoutType.TOP; | 214 options.DisplayLayoutType.TOP; |
529 break; | 215 break; |
530 case options.DisplayLayoutType.BOTTOM: | 216 case options.DisplayLayoutType.BOTTOM: |
531 layoutType = isPrimary ? options.DisplayLayoutType.TOP : | 217 layoutType = isPrimary ? options.DisplayLayoutType.TOP : |
532 options.DisplayLayoutType.BOTTOM; | 218 options.DisplayLayoutType.BOTTOM; |
533 break; | 219 break; |
534 } | 220 } |
535 | 221 |
536 if (layoutType == options.DisplayLayoutType.LEFT || | 222 if (layoutType == options.DisplayLayoutType.LEFT || |
537 layoutType == options.DisplayLayoutType.RIGHT) { | 223 layoutType == options.DisplayLayoutType.RIGHT) { |
538 if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight) | 224 if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight) |
539 layoutType = isPrimary ? options.DisplayLayoutType.TOP : | 225 layoutType = isPrimary ? options.DisplayLayoutType.TOP : |
540 options.DisplayLayoutType.BOTTOM; | 226 options.DisplayLayoutType.BOTTOM; |
541 else if (newPosition.y + draggingDiv.offsetHeight < baseDiv.offsetTop) | 227 else if (newPosition.y + div.offsetHeight < baseDiv.offsetTop) |
542 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : | 228 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : |
543 options.DisplayLayoutType.TOP; | 229 options.DisplayLayoutType.TOP; |
544 } else { | 230 } else { |
545 if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth) | 231 if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth) |
546 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : | 232 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : |
547 options.DisplayLayoutType.RIGHT; | 233 options.DisplayLayoutType.RIGHT; |
548 else if (newPosition.x + draggingDiv.offsetWidth < baseDiv.offsetLeft) | 234 else if (newPosition.x + div.offsetWidth < baseDiv.offsetLeft) |
549 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : | 235 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : |
550 options.DisplayLayoutType.LEFT; | 236 options.DisplayLayoutType.LEFT; |
551 } | 237 } |
552 | 238 |
553 var layoutToBase; | 239 var layoutToBase; |
554 if (!isPrimary) { | 240 if (!isPrimary) { |
555 dragLayout.layoutType = layoutType; | 241 displayLayout.layoutType = layoutType; |
556 layoutToBase = layoutType; | 242 layoutToBase = layoutType; |
557 } else { | 243 } else { |
558 baseLayout.layoutType = layoutType; | 244 baseLayout.layoutType = layoutType; |
559 switch (layoutType) { | 245 switch (layoutType) { |
560 case options.DisplayLayoutType.RIGHT: | 246 case options.DisplayLayoutType.RIGHT: |
561 layoutToBase = options.DisplayLayoutType.LEFT; | 247 layoutToBase = options.DisplayLayoutType.LEFT; |
562 break; | 248 break; |
563 case options.DisplayLayoutType.LEFT: | 249 case options.DisplayLayoutType.LEFT: |
564 layoutToBase = options.DisplayLayoutType.RIGHT; | 250 layoutToBase = options.DisplayLayoutType.RIGHT; |
565 break; | 251 break; |
566 case options.DisplayLayoutType.TOP: | 252 case options.DisplayLayoutType.TOP: |
567 layoutToBase = options.DisplayLayoutType.BOTTOM; | 253 layoutToBase = options.DisplayLayoutType.BOTTOM; |
568 break; | 254 break; |
569 case options.DisplayLayoutType.BOTTOM: | 255 case options.DisplayLayoutType.BOTTOM: |
570 layoutToBase = options.DisplayLayoutType.TOP; | 256 layoutToBase = options.DisplayLayoutType.TOP; |
571 break; | 257 break; |
572 } | 258 } |
573 } | 259 } |
574 | 260 |
575 switch (layoutToBase) { | 261 switch (layoutToBase) { |
576 case options.DisplayLayoutType.RIGHT: | 262 case options.DisplayLayoutType.RIGHT: |
577 draggingDiv.style.left = | 263 div.style.left = baseDiv.offsetLeft + baseDiv.offsetWidth + 'px'; |
578 baseDiv.offsetLeft + baseDiv.offsetWidth + 'px'; | 264 div.style.top = newPosition.y + 'px'; |
579 draggingDiv.style.top = newPosition.y + 'px'; | |
580 break; | 265 break; |
581 case options.DisplayLayoutType.LEFT: | 266 case options.DisplayLayoutType.LEFT: |
582 draggingDiv.style.left = | 267 div.style.left = baseDiv.offsetLeft - div.offsetWidth + 'px'; |
583 baseDiv.offsetLeft - draggingDiv.offsetWidth + 'px'; | 268 div.style.top = newPosition.y + 'px'; |
584 draggingDiv.style.top = newPosition.y + 'px'; | |
585 break; | 269 break; |
586 case options.DisplayLayoutType.TOP: | 270 case options.DisplayLayoutType.TOP: |
587 draggingDiv.style.top = | 271 div.style.top = baseDiv.offsetTop - div.offsetHeight + 'px'; |
588 baseDiv.offsetTop - draggingDiv.offsetHeight + 'px'; | 272 div.style.left = newPosition.x + 'px'; |
589 draggingDiv.style.left = newPosition.x + 'px'; | |
590 break; | 273 break; |
591 case options.DisplayLayoutType.BOTTOM: | 274 case options.DisplayLayoutType.BOTTOM: |
592 draggingDiv.style.top = | 275 div.style.top = baseDiv.offsetTop + baseDiv.offsetHeight + 'px'; |
593 baseDiv.offsetTop + baseDiv.offsetHeight + 'px'; | 276 div.style.left = newPosition.x + 'px'; |
594 draggingDiv.style.left = newPosition.x + 'px'; | |
595 break; | 277 break; |
596 } | 278 } |
597 | |
598 return false; | |
599 }, | 279 }, |
600 | 280 |
601 /** | 281 /** |
602 * Start dragging of a display rectangle. | 282 * Finalize the location of display |id| after updates are complete |
603 * @param {!HTMLElement} target The event target. | 283 * (e.g. draging has ended). |
604 * @param {!options.DisplayPosition} eventLocation The event location. | 284 * @param {string} id |
605 * @private | 285 * @return {boolean} True if the final position differs from the original. |
606 */ | 286 */ |
607 startDragging_: function(target, eventLocation) { | 287 finalizePosition: function(id) { |
608 var oldFocusedId = this.focusedId_; | 288 // Make sure the dragging location is connected. |
609 var newFocusedId; | 289 var displayLayout = this.displayLayoutMap_[id]; |
610 var willUpdateDisplayDescription = false; | 290 var div = displayLayout.div; |
611 for (var i = 0; i < this.displays_.length; i++) { | 291 var baseLayout = this.getBaseLayout_(displayLayout); |
612 var displayLayout = this.displayLayoutMap_[this.displays_[i].id]; | 292 var baseDiv = baseLayout.div; |
613 if (displayLayout.div == target || | |
614 (target.offsetParent && target.offsetParent == displayLayout.div)) { | |
615 newFocusedId = displayLayout.id; | |
616 break; | |
617 } | |
618 } | |
619 if (!newFocusedId) | |
620 return false; | |
621 | 293 |
622 this.focusedId_ = newFocusedId; | 294 var isPrimary = displayLayout.parentId == ''; |
623 willUpdateDisplayDescription = newFocusedId != oldFocusedId; | 295 var layoutType = |
296 isPrimary ? baseLayout.layoutType : displayLayout.layoutType; | |
624 | 297 |
625 for (var i = 0; i < this.displays_.length; i++) { | 298 // The number of pixels to share the edges between displays. |
626 var displayLayout = this.displayLayoutMap_[this.displays_[i].id]; | 299 /** @const */ var MIN_OFFSET_OVERLAP = 5; |
627 displayLayout.div.className = 'displays-display'; | |
628 if (displayLayout.id != this.focusedId_) | |
629 continue; | |
630 | |
631 displayLayout.div.classList.add('displays-focused'); | |
632 if (this.displays_.length > 1) { | |
633 this.dragInfo_ = { | |
634 displayId: displayLayout.id, | |
635 originalLocation: { | |
636 x: displayLayout.div.offsetLeft, | |
637 y: displayLayout.div.offsetTop | |
638 }, | |
639 eventLocation: {x: eventLocation.x, y: eventLocation.y} | |
640 }; | |
641 } | |
642 } | |
643 | |
644 if (willUpdateDisplayDescription) | |
645 this.updateSelectedDisplayDescription_(); | |
646 return false; | |
647 }, | |
648 | |
649 /** | |
650 * finish the current dragging of displays. | |
651 * @param {Event} e The event which triggers this. | |
652 * @private | |
653 */ | |
654 endDragging_: function(e) { | |
655 this.lastTouchLocation_ = null; | |
656 if (!this.dragInfo_) | |
657 return false; | |
658 | |
659 // Make sure the dragging location is connected. | |
660 var dragLayout = this.displayLayoutMap_[this.dragInfo_.displayId]; | |
661 var baseDisplayId = dragLayout.isPrimary ? this.secondaryDisplayId_ : | |
662 this.primaryDisplayId_; | |
663 | |
664 var baseLayout = this.displayLayoutMap_[baseDisplayId]; | |
665 var baseDiv = baseLayout.div; | |
666 var draggingDiv = dragLayout.div; | |
667 | |
668 // layoutType is always stored in the secondary layout. | |
669 var layoutType = | |
670 dragLayout.isPrimary ? baseLayout.layoutType : dragLayout.layoutType; | |
671 | 300 |
672 if (layoutType == options.DisplayLayoutType.LEFT || | 301 if (layoutType == options.DisplayLayoutType.LEFT || |
673 layoutType == options.DisplayLayoutType.RIGHT) { | 302 layoutType == options.DisplayLayoutType.RIGHT) { |
674 var top = Math.max( | 303 var top = Math.max( |
675 draggingDiv.offsetTop, | 304 div.offsetTop, |
676 baseDiv.offsetTop - draggingDiv.offsetHeight + MIN_OFFSET_OVERLAP); | 305 baseDiv.offsetTop - div.offsetHeight + MIN_OFFSET_OVERLAP); |
677 top = Math.min( | 306 top = Math.min( |
678 top, baseDiv.offsetTop + baseDiv.offsetHeight - MIN_OFFSET_OVERLAP); | 307 top, baseDiv.offsetTop + baseDiv.offsetHeight - MIN_OFFSET_OVERLAP); |
679 draggingDiv.style.top = top + 'px'; | 308 div.style.top = top + 'px'; |
680 } else { | 309 } else { |
681 var left = Math.max( | 310 var left = Math.max( |
682 draggingDiv.offsetLeft, | 311 div.offsetLeft, |
683 baseDiv.offsetLeft - draggingDiv.offsetWidth + MIN_OFFSET_OVERLAP); | 312 baseDiv.offsetLeft - div.offsetWidth + MIN_OFFSET_OVERLAP); |
684 left = Math.min( | 313 left = Math.min( |
685 left, | 314 left, |
686 baseDiv.offsetLeft + baseDiv.offsetWidth - MIN_OFFSET_OVERLAP); | 315 baseDiv.offsetLeft + baseDiv.offsetWidth - MIN_OFFSET_OVERLAP); |
687 draggingDiv.style.left = left + 'px'; | 316 div.style.left = left + 'px'; |
688 } | |
689 if (dragLayout.originalPosition.x != draggingDiv.offsetLeft || | |
690 dragLayout.originalPosition.y != draggingDiv.offsetTop) { | |
691 this.sendDragResult_(); | |
692 } | 317 } |
693 | 318 |
694 this.dragInfo_ = null; | 319 // Calculate the offset of the child display. |
320 this.calculateOffset_(isPrimary ? baseLayout : displayLayout); | |
695 | 321 |
696 return false; | 322 return displayLayout.originalPosition.x != div.offsetLeft || |
323 displayLayout.originalPosition.y != div.offsetTop; | |
697 }, | 324 }, |
698 | 325 |
699 /** | 326 /** |
700 * Updates the description of selected display section for mirroring mode. | 327 * Creates a div element and assigns it to |displayLayout|. Returns the |
701 * @private | 328 * created div for additional decoration. |
329 * @param {string} id | |
330 * @param {!Element} parentElement The parent element to contain the div. | |
331 * @param {!options.DisplayPosition} offset The offset to the center of | |
332 * the display area. | |
702 */ | 333 */ |
703 updateSelectedDisplaySectionMirroring_: function() { | 334 createDisplayLayoutDiv_: function(id, parentElement, offset) { |
704 $('display-configuration-arrow').hidden = true; | 335 var displayLayout = this.displayLayoutMap_[id]; |
705 $('display-options-set-primary').disabled = true; | 336 var parentId = displayLayout.parentId; |
706 $('display-options-select-mirroring').disabled = false; | 337 if (parentId) { |
707 $('selected-display-start-calibrating-overscan').disabled = true; | 338 // Ensure the parent div is created first. |
708 var display = this.displays_[0]; | 339 var parentLayout = this.displayLayoutMap_[parentId]; |
709 var orientation = $('display-options-orientation-selection'); | 340 if (!parentLayout.div) |
710 orientation.disabled = false; | 341 this.createDisplayLayoutDiv_(parentId, parentElement, offset); |
711 var orientationOptions = orientation.getElementsByTagName('option'); | |
712 var orientationIndex = Math.floor(display.rotation / 90); | |
713 orientationOptions[orientationIndex].selected = true; | |
714 $('selected-display-name').textContent = | |
715 loadTimeData.getString('mirroringDisplay'); | |
716 var resolution = $('display-options-resolution-selection'); | |
717 var option = document.createElement('option'); | |
718 option.value = 'default'; | |
719 option.textContent = display.bounds.width + 'x' + display.bounds.height; | |
720 resolution.appendChild(option); | |
721 resolution.disabled = true; | |
722 }, | |
723 | |
724 /** | |
725 * Updates the description of selected display section when no display is | |
726 * selected. | |
727 * @private | |
728 */ | |
729 updateSelectedDisplaySectionNoSelected_: function() { | |
730 $('display-configuration-arrow').hidden = true; | |
731 $('display-options-set-primary').disabled = true; | |
732 $('display-options-select-mirroring').disabled = true; | |
733 $('selected-display-start-calibrating-overscan').disabled = true; | |
734 $('display-options-orientation-selection').disabled = true; | |
735 $('selected-display-name').textContent = ''; | |
736 var resolution = $('display-options-resolution-selection'); | |
737 resolution.appendChild(document.createElement('option')); | |
738 resolution.disabled = true; | |
739 }, | |
740 | |
741 /** | |
742 * Updates the description of selected display section for the selected | |
743 * display. | |
744 * @param {options.DisplayInfo} display The selected display object. | |
745 * @private | |
746 */ | |
747 updateSelectedDisplaySectionForDisplay_: function(display) { | |
748 var displayLayout = this.displayLayoutMap_[display.id]; | |
749 var arrow = $('display-configuration-arrow'); | |
750 arrow.hidden = false; | |
751 // Adding 1 px to the position to fit the border line and the border in | |
752 // arrow precisely. | |
753 arrow.style.top = $('display-configurations').offsetTop - | |
754 arrow.offsetHeight / 2 + 'px'; | |
755 arrow.style.left = displayLayout.div.offsetLeft + | |
756 displayLayout.div.offsetWidth / 2 - arrow.offsetWidth / 2 + 'px'; | |
757 | |
758 $('display-options-set-primary').disabled = display.isPrimary; | |
759 $('display-options-select-mirroring').disabled = | |
760 (this.displays_.length <= 1 && !this.unifiedDesktopEnabled_); | |
761 $('selected-display-start-calibrating-overscan').disabled = | |
762 display.isInternal; | |
763 | |
764 var orientation = $('display-options-orientation-selection'); | |
765 orientation.disabled = this.unifiedDesktopEnabled_; | |
766 | |
767 var orientationOptions = orientation.getElementsByTagName('option'); | |
768 var orientationIndex = Math.floor(display.rotation / 90); | |
769 orientationOptions[orientationIndex].selected = true; | |
770 | |
771 $('selected-display-name').textContent = display.name; | |
772 | |
773 var resolution = $('display-options-resolution-selection'); | |
774 if (display.resolutions.length <= 1) { | |
775 var option = document.createElement('option'); | |
776 option.value = 'default'; | |
777 option.textContent = display.bounds.width + 'x' + display.bounds.height; | |
778 option.selected = true; | |
779 resolution.appendChild(option); | |
780 resolution.disabled = true; | |
781 } else { | |
782 var previousOption; | |
783 for (var i = 0; i < display.resolutions.length; i++) { | |
784 var option = document.createElement('option'); | |
785 option.value = i; | |
786 option.textContent = display.resolutions[i].width + 'x' + | |
787 display.resolutions[i].height; | |
788 if (display.resolutions[i].isBest) { | |
789 option.textContent += ' ' + | |
790 loadTimeData.getString('annotateBest'); | |
791 } else if (display.resolutions[i].isNative) { | |
792 option.textContent += ' ' + | |
793 loadTimeData.getString('annotateNative'); | |
794 } | |
795 if (display.resolutions[i].deviceScaleFactor && previousOption && | |
796 previousOption.textContent == option.textContent) { | |
797 option.textContent += | |
798 ' (' + display.resolutions[i].deviceScaleFactor + 'x)'; | |
799 } | |
800 option.selected = display.resolutions[i].selected; | |
801 resolution.appendChild(option); | |
802 previousOption = option; | |
803 } | |
804 resolution.disabled = (display.resolutions.length <= 1); | |
805 } | 342 } |
806 | 343 |
807 if (display.availableColorProfiles.length <= 1) { | |
808 $('selected-display-color-profile-row').hidden = true; | |
809 } else { | |
810 $('selected-display-color-profile-row').hidden = false; | |
811 var profiles = $('display-options-color-profile-selection'); | |
812 profiles.innerHTML = ''; | |
813 for (var i = 0; i < display.availableColorProfiles.length; i++) { | |
814 var option = document.createElement('option'); | |
815 var colorProfile = display.availableColorProfiles[i]; | |
816 option.value = colorProfile.profileId; | |
817 option.textContent = colorProfile.name; | |
818 option.selected = (display.colorProfileId == colorProfile.profileId); | |
819 profiles.appendChild(option); | |
820 } | |
821 } | |
822 }, | |
823 | |
824 /** | |
825 * Updates the description of the selected display section. | |
826 * @private | |
827 */ | |
828 updateSelectedDisplayDescription_: function() { | |
829 var resolution = $('display-options-resolution-selection'); | |
830 resolution.textContent = ''; | |
831 var orientation = $('display-options-orientation-selection'); | |
832 var orientationOptions = orientation.getElementsByTagName('option'); | |
833 for (var i = 0; i < orientationOptions.length; i++) | |
834 orientationOptions[i].selected = false; | |
835 | |
836 if (this.mirroring_) { | |
837 this.updateSelectedDisplaySectionMirroring_(); | |
838 } else if (this.focusedId_ == '') { | |
839 this.updateSelectedDisplaySectionNoSelected_(); | |
840 } else { | |
841 this.updateSelectedDisplaySectionForDisplay_( | |
842 this.getDisplayInfoFromId(this.focusedId_)); | |
843 } | |
844 }, | |
845 | |
846 /** | |
847 * Clears the drawing area for display rectangles. | |
848 * @private | |
849 */ | |
850 resetDisplaysView_: function() { | |
851 var displaysViewHost = $('display-options-displays-view-host'); | |
852 displaysViewHost.removeChild(displaysViewHost.firstChild); | |
853 this.displaysView_ = document.createElement('div'); | |
854 this.displaysView_.id = 'display-options-displays-view'; | |
855 displaysViewHost.appendChild(this.displaysView_); | |
856 }, | |
857 | |
858 /** | |
859 * Lays out the display rectangles for mirroring. | |
860 * @private | |
861 */ | |
862 layoutMirroringDisplays_: function() { | |
863 // Offset pixels for secondary display rectangles. The offset includes the | |
864 // border width. | |
865 /** @const */ var MIRRORING_OFFSET_PIXELS = 3; | |
866 // Always show two displays because there must be two displays when | |
867 // the display_options is enabled. Don't rely on displays_.length because | |
868 // there is only one display from chrome's perspective in mirror mode. | |
869 /** @const */ var MIN_NUM_DISPLAYS = 2; | |
870 /** @const */ var MIRRORING_VERTICAL_MARGIN = 20; | |
871 | |
872 // The width/height should be same as the first display: | |
873 var width = Math.ceil(this.displays_[0].bounds.width * this.visualScale_); | |
874 var height = | |
875 Math.ceil(this.displays_[0].bounds.height * this.visualScale_); | |
876 | |
877 var numDisplays = Math.max(MIN_NUM_DISPLAYS, this.displays_.length); | |
878 | |
879 var totalWidth = width + numDisplays * MIRRORING_OFFSET_PIXELS; | |
880 var totalHeight = height + numDisplays * MIRRORING_OFFSET_PIXELS; | |
881 | |
882 this.displaysView_.style.height = totalHeight + 'px'; | |
883 | |
884 // The displays should be centered. | |
885 var offsetX = | |
886 $('display-options-displays-view').offsetWidth / 2 - totalWidth / 2; | |
887 | |
888 for (var i = 0; i < numDisplays; i++) { | |
889 var div = /** @type {HTMLElement} */ (document.createElement('div')); | |
890 div.className = 'displays-display'; | |
891 div.style.top = i * MIRRORING_OFFSET_PIXELS + 'px'; | |
892 div.style.left = i * MIRRORING_OFFSET_PIXELS + offsetX + 'px'; | |
893 div.style.width = width + 'px'; | |
894 div.style.height = height + 'px'; | |
895 div.style.zIndex = i; | |
896 // set 'display-mirrored' class for the background display rectangles. | |
897 if (i != numDisplays - 1) | |
898 div.classList.add('display-mirrored'); | |
899 this.displaysView_.appendChild(div); | |
900 } | |
901 }, | |
902 | |
903 /** | |
904 * Creates a DisplayLayout object representing the display. | |
905 * @param {!options.DisplayInfo} display | |
906 * @param {!options.DisplayLayoutType} layoutType | |
907 * @return {!options.DisplayLayout} | |
908 * @private | |
909 */ | |
910 createDisplayLayout_: function(display, layoutType) { | |
911 return { | |
912 bounds: display.bounds, | |
913 div: null, | |
914 id: display.id, | |
915 isPrimary: display.isPrimary, | |
916 layoutType: layoutType, | |
917 name: display.name, | |
918 originalPosition: {x: 0, y: 0} | |
919 }; | |
920 }, | |
921 | |
922 /** | |
923 * Creates a div element representing the specified display. | |
924 * @param {!options.DisplayLayout} displayLayout | |
925 * @param {options.DisplayLayoutType} layoutType The layout type for the | |
926 * secondary display. | |
927 * @param {!options.DisplayPosition} offset The offset to the center of the | |
928 * display area. | |
929 * @private | |
930 */ | |
931 createDisplayLayoutDiv_: function(displayLayout, layoutType, offset) { | |
932 var div = /** @type {!HTMLElement} */ (document.createElement('div')); | 344 var div = /** @type {!HTMLElement} */ (document.createElement('div')); |
933 div.className = 'displays-display'; | 345 div.className = 'displays-display'; |
934 div.classList.toggle( | |
935 'displays-focused', displayLayout.id == this.focusedId_); | |
936 | 346 |
937 // div needs to be added to the DOM tree first, otherwise offsetHeight for | 347 // div needs to be added to the DOM tree first, otherwise offsetHeight for |
938 // nameContainer below cannot be computed. | 348 // nameContainer below cannot be computed. |
939 this.displaysView_.appendChild(div); | 349 parentElement.appendChild(div); |
940 | 350 |
941 var nameContainer = document.createElement('div'); | 351 var nameContainer = document.createElement('div'); |
942 nameContainer.textContent = displayLayout.name; | 352 nameContainer.textContent = displayLayout.name; |
943 div.appendChild(nameContainer); | 353 div.appendChild(nameContainer); |
944 | 354 |
945 var bounds = displayLayout.bounds; | 355 var bounds = displayLayout.bounds; |
946 div.style.width = Math.floor(bounds.width * this.visualScale_) + 'px'; | 356 div.style.width = Math.floor(bounds.width * this.visualScale_) + 'px'; |
947 var newHeight = Math.floor(bounds.height * this.visualScale_); | 357 var newHeight = Math.floor(bounds.height * this.visualScale_); |
948 div.style.height = newHeight + 'px'; | 358 div.style.height = newHeight + 'px'; |
949 nameContainer.style.marginTop = | 359 nameContainer.style.marginTop = |
950 (newHeight - nameContainer.offsetHeight) / 2 + 'px'; | 360 (newHeight - nameContainer.offsetHeight) / 2 + 'px'; |
951 | 361 |
952 div.onmousedown = this.onMouseDown_.bind(this); | 362 if (displayLayout.parentId == '') { |
953 div.ontouchstart = this.onTouchStart_.bind(this); | |
954 | |
955 if (displayLayout.isPrimary) { | |
956 div.style.left = | 363 div.style.left = |
957 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; | 364 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; |
958 div.style.top = | 365 div.style.top = |
959 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; | 366 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; |
960 } else { | 367 } else { |
961 // Don't trust the secondary display's x or y, because it may cause a | 368 // Don't trust the child display's x or y, because it may cause a |
962 // 1px gap due to rounding, which will create a fake update on end | 369 // 1px gap due to rounding, which will create a fake update on end |
963 // dragging. See crbug.com/386401 | 370 // dragging. See crbug.com/386401 |
964 var primaryDiv = this.displayLayoutMap_[this.primaryDisplayId_].div; | 371 var parentDiv = this.displayLayoutMap_[displayLayout.parentId].div; |
965 switch (layoutType) { | 372 switch (displayLayout.layoutType) { |
966 case options.DisplayLayoutType.TOP: | 373 case options.DisplayLayoutType.TOP: |
967 div.style.left = | 374 div.style.left = |
968 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; | 375 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; |
969 div.style.top = primaryDiv.offsetTop - div.offsetHeight + 'px'; | 376 div.style.top = parentDiv.offsetTop - div.offsetHeight + 'px'; |
970 break; | 377 break; |
971 case options.DisplayLayoutType.RIGHT: | 378 case options.DisplayLayoutType.RIGHT: |
972 div.style.left = | 379 div.style.left = |
973 primaryDiv.offsetLeft + primaryDiv.offsetWidth + 'px'; | 380 parentDiv.offsetLeft + parentDiv.offsetWidth + 'px'; |
974 div.style.top = | 381 div.style.top = |
975 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; | 382 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; |
976 break; | 383 break; |
977 case options.DisplayLayoutType.BOTTOM: | 384 case options.DisplayLayoutType.BOTTOM: |
978 div.style.left = | 385 div.style.left = |
979 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; | 386 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; |
980 div.style.top = | 387 div.style.top = parentDiv.offsetTop + parentDiv.offsetHeight + 'px'; |
981 primaryDiv.offsetTop + primaryDiv.offsetHeight + 'px'; | |
982 break; | 388 break; |
983 case options.DisplayLayoutType.LEFT: | 389 case options.DisplayLayoutType.LEFT: |
984 div.style.left = primaryDiv.offsetLeft - div.offsetWidth + 'px'; | 390 div.style.left = parentDiv.offsetLeft - div.offsetWidth + 'px'; |
985 div.style.top = | 391 div.style.top = |
986 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; | 392 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; |
987 break; | 393 break; |
988 } | 394 } |
989 } | 395 } |
990 | 396 |
991 displayLayout.div = div; | 397 displayLayout.div = div; |
992 displayLayout.originalPosition.x = div.offsetLeft; | 398 displayLayout.originalPosition.x = div.offsetLeft; |
993 displayLayout.originalPosition.y = div.offsetTop; | 399 displayLayout.originalPosition.y = div.offsetTop; |
400 | |
401 this.calculateOffset_(displayLayout); | |
994 }, | 402 }, |
995 | 403 |
996 /** | 404 /** |
997 * Layouts the display rectangles according to the current layout_. | 405 * Calculates the offset for display |id| relative to its parent. |
998 * @param {options.DisplayLayoutType} layoutType | 406 * @param {options.DisplayLayout} displayLayout |
999 * @private | |
1000 */ | 407 */ |
1001 layoutDisplays_: function(layoutType) { | 408 calculateOffset_: function(displayLayout) { |
1002 var maxWidth = 0; | 409 // Offset is calculated from top or left edge. |
1003 var maxHeight = 0; | 410 var parent = this.displayLayoutMap_[displayLayout.parentId]; |
1004 var boundingBox = {left: 0, right: 0, top: 0, bottom: 0}; | 411 if (!parent) { |
1005 this.primaryDisplayId_ = ''; | 412 displayLayout.offset = 0; |
1006 this.secondaryDisplayId_ = ''; | 413 return; |
1007 for (var i = 0; i < this.displays_.length; i++) { | |
1008 var display = this.displays_[i]; | |
1009 if (display.isPrimary) | |
1010 this.primaryDisplayId_ = display.id; | |
1011 else if (this.secondaryDisplayId_ == '') | |
1012 this.secondaryDisplayId_ = display.id; | |
1013 | |
1014 var bounds = display.bounds; | |
1015 boundingBox.left = Math.min(boundingBox.left, bounds.left); | |
1016 boundingBox.right = | |
1017 Math.max(boundingBox.right, bounds.left + bounds.width); | |
1018 boundingBox.top = Math.min(boundingBox.top, bounds.top); | |
1019 boundingBox.bottom = | |
1020 Math.max(boundingBox.bottom, bounds.top + bounds.height); | |
1021 maxWidth = Math.max(maxWidth, bounds.width); | |
1022 maxHeight = Math.max(maxHeight, bounds.height); | |
1023 } | 414 } |
1024 if (this.primaryDisplayId_ == '') | 415 var offset; |
1025 return; | 416 if (displayLayout.layoutType == options.DisplayLayoutType.LEFT || |
1026 | 417 displayLayout.layoutType == options.DisplayLayoutType.RIGHT) { |
1027 // Make the margin around the bounding box. | 418 offset = displayLayout.div.offsetTop - parent.div.offsetTop; |
1028 var areaWidth = boundingBox.right - boundingBox.left + maxWidth; | 419 } else { |
1029 var areaHeight = boundingBox.bottom - boundingBox.top + maxHeight; | 420 offset = displayLayout.div.offsetLeft - parent.div.offsetLeft; |
1030 | |
1031 // Calculates the scale by the width since horizontal size is more strict. | |
1032 // TODO(mukai): Adds the check of vertical size in case. | |
1033 this.visualScale_ = Math.min( | |
1034 VISUAL_SCALE, this.displaysView_.offsetWidth / areaWidth); | |
1035 | |
1036 // Prepare enough area for thisplays_view by adding the maximum height. | |
1037 this.displaysView_.style.height = | |
1038 Math.ceil(areaHeight * this.visualScale_) + 'px'; | |
1039 | |
1040 // Centering the bounding box of the display rectangles. | |
1041 var offset = { | |
1042 x: Math.floor( | |
1043 this.displaysView_.offsetWidth / 2 - | |
1044 (boundingBox.right + boundingBox.left) * this.visualScale_ / 2), | |
1045 y: Math.floor( | |
1046 this.displaysView_.offsetHeight / 2 - | |
1047 (boundingBox.bottom + boundingBox.top) * this.visualScale_ / 2) | |
1048 }; | |
1049 | |
1050 // Layout the display rectangles. First layout the primary display and | |
1051 // then layout any secondary displays. | |
1052 this.createDisplayLayoutDiv_( | |
1053 this.displayLayoutMap_[this.primaryDisplayId_], layoutType, offset); | |
1054 for (var i = 0; i < this.displays_.length; i++) { | |
1055 if (!this.displays_[i].isPrimary) { | |
1056 this.createDisplayLayoutDiv_( | |
1057 this.displayLayoutMap_[this.displays_[i].id], layoutType, offset); | |
1058 } | |
1059 } | 421 } |
422 displayLayout.offset = Math.floor(offset / this.visualScale_); | |
1060 }, | 423 }, |
1061 | 424 |
1062 /** | 425 /** |
1063 * Called when the display arrangement has changed. | 426 * Returns the display paired with |displayLayout|, either the parent |
1064 * @param {options.MultiDisplayMode} mode multi display mode. | 427 * or a matching child (currently there will only be one). |
1065 * @param {!Array<!options.DisplayInfo>} displays The list of the display | 428 * @param {options.DisplayLayout} displayLayout |
1066 * information. | 429 * @return {?options.DisplayLayout} |
1067 * @param {options.DisplayLayoutType} layoutType The layout strategy. | |
1068 * @param {number} offset The offset of the secondary display. | |
1069 * @private | 430 * @private |
1070 */ | 431 */ |
1071 onDisplayChanged_: function(mode, displays, layoutType, offset) { | 432 getBaseLayout_(displayLayout) { |
1072 if (!this.visible) | 433 if (displayLayout.parentId != '') |
1073 return; | 434 return this.displayLayoutMap_[displayLayout.parentId]; |
1074 | 435 for (var childId in this.displayLayoutMap_) { |
1075 this.displays_ = displays; | 436 var child = this.displayLayoutMap_[childId]; |
1076 this.displayLayoutMap_ = {}; | 437 if (child.parentId == displayLayout.id) |
1077 for (var i = 0; i < displays.length; i++) { | 438 return child; |
1078 var display = displays[i]; | |
1079 this.displayLayoutMap_[display.id] = | |
1080 this.createDisplayLayout_(display, layoutType); | |
1081 } | 439 } |
1082 | 440 assertNotReached(); |
1083 var mirroring = mode == options.MultiDisplayMode.MIRRORING; | 441 return null; |
1084 var unifiedDesktopEnabled = mode == options.MultiDisplayMode.UNIFIED; | |
1085 | |
1086 // Focus to the first display next to the primary one when |displays_| | |
1087 // is updated. | |
1088 if (mirroring) { | |
1089 this.focusedId_ = ''; | |
1090 } else if ( | |
1091 this.focusedId_ == '' || this.mirroring_ != mirroring || | |
1092 this.unifiedDesktopEnabled_ != unifiedDesktopEnabled || | |
1093 this.displays_.length != displays.length) { | |
1094 this.focusedId_ = displays.length > 0 ? displays[0].id : ''; | |
1095 } | |
1096 | |
1097 this.mirroring_ = mirroring; | |
1098 this.unifiedDesktopEnabled_ = unifiedDesktopEnabled; | |
1099 | |
1100 this.resetDisplaysView_(); | |
1101 if (this.mirroring_) | |
1102 this.layoutMirroringDisplays_(); | |
1103 else | |
1104 this.layoutDisplays_(layoutType); | |
1105 | |
1106 $('display-options-select-mirroring').value = | |
1107 mirroring ? 'mirroring' : 'extended'; | |
1108 | |
1109 $('display-options-unified-desktop').hidden = | |
1110 !this.showUnifiedDesktopOption_; | |
1111 | |
1112 $('display-options-toggle-unified-desktop').checked = | |
1113 this.unifiedDesktopEnabled_; | |
1114 | |
1115 var disableUnifiedDesktopOption = | |
1116 (this.mirroring_ || | |
1117 (!this.unifiedDesktopEnabled_ && | |
1118 this.displays_.length == 1)); | |
1119 | |
1120 $('display-options-toggle-unified-desktop').disabled = | |
1121 disableUnifiedDesktopOption; | |
1122 | |
1123 this.updateSelectedDisplayDescription_(); | |
1124 } | 442 } |
1125 }; | 443 }; |
1126 | 444 |
1127 DisplayOptions.setDisplayInfo = function( | |
1128 mode, displays, layoutType, offset) { | |
1129 DisplayOptions.getInstance().onDisplayChanged_( | |
1130 mode, displays, layoutType, offset); | |
1131 }; | |
1132 | |
1133 // Export | 445 // Export |
1134 return { | 446 return {DisplayLayoutManager: DisplayLayoutManager}; |
1135 DisplayOptions: DisplayOptions | |
1136 }; | |
1137 }); | 447 }); |
OLD | NEW |