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

Side by Side Diff: chrome/browser/resources/options/chromeos/display_layout_manager.js

Issue 1626573003: Extract DisplayLayoutManager from display_options.js (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase off 1633983002 + fixes Created 4 years, 11 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 (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
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 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698