1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 cr
.exportPath('options');
9 * availableColorProfiles: Array<{profileId: number, name: string}>,
10 * colorProfile: number,
13 * isInternal: boolean,
15 * resolutions: Array<{width: number, height: number, originalWidth: number,
16 * originalHeight: number, deviceScaleFactor: number, scale: number,
17 * refreshRate: number, isBest: boolean, selected: boolean}>,
19 * orientation: number,
28 * Enumeration of secondary display layout. The value has to be same as the
29 * values in ash/display/display_controller.cc.
32 options
.SecondaryDisplayLayout
= {
40 * Enumeration of multi display mode. The value has to be same as the
41 * values in ash/display/display_manager..
44 options
.MultiDisplayMode
= {
50 cr
.define('options', function() {
51 var Page
= cr
.ui
.pageManager
.Page
;
52 var PageManager
= cr
.ui
.pageManager
.PageManager
;
54 // The scale ratio of the display rectangle to its original size.
55 /** @const */ var VISUAL_SCALE
= 1 / 10;
57 // The number of pixels to share the edges between displays.
58 /** @const */ var MIN_OFFSET_OVERLAP
= 5;
61 * Calculates the bounds of |element| relative to the page.
62 * @param {HTMLElement} element The element to be known.
63 * @return {Object} The object for the bounds, with x, y, width, and height.
65 function getBoundsInPage(element
) {
67 x
: element
.offsetLeft
,
69 width
: element
.offsetWidth
,
70 height
: element
.offsetHeight
72 var parent
= element
.offsetParent
;
73 while (parent
&& parent
!= document
.body
) {
74 bounds
.x
+= parent
.offsetLeft
;
75 bounds
.y
+= parent
.offsetTop
;
76 parent
= parent
.offsetParent
;
82 * Gets the position of |point| to |rect|, left, right, top, or bottom.
83 * @param {Object} rect The base rectangle with x, y, width, and height.
84 * @param {Object} point The point to check the position.
85 * @return {options.SecondaryDisplayLayout} The position of the calculated
88 function getPositionToRectangle(rect
, point
) {
89 // Separates the area into four (LEFT/RIGHT/TOP/BOTTOM) by the diagonals of
90 // the rect, and decides which area the display should reside.
91 var diagonalSlope
= rect
.height
/ rect
.width
;
92 var topDownIntercept
= rect
.y
- rect
.x
* diagonalSlope
;
93 var bottomUpIntercept
= rect
.y
+ rect
.height
+ rect
.x
* diagonalSlope
;
95 if (point
.y
> topDownIntercept
+ point
.x
* diagonalSlope
) {
96 if (point
.y
> bottomUpIntercept
- point
.x
* diagonalSlope
)
97 return options
.SecondaryDisplayLayout
.BOTTOM
;
99 return options
.SecondaryDisplayLayout
.LEFT
;
101 if (point
.y
> bottomUpIntercept
- point
.x
* diagonalSlope
)
102 return options
.SecondaryDisplayLayout
.RIGHT
;
104 return options
.SecondaryDisplayLayout
.TOP
;
109 * Encapsulated handling of the 'Display' page.
111 * @extends {cr.ui.pageManager.Page}
113 function DisplayOptions() {
114 Page
.call(this, 'display',
115 loadTimeData
.getString('displayOptionsPageTabTitle'),
116 'display-options-page');
119 cr
.addSingletonGetter(DisplayOptions
);
121 DisplayOptions
.prototype = {
122 __proto__
: Page
.prototype,
125 * Whether the current output status is mirroring displays or not.
131 * Whether the unified desktop is enable or not.
134 unifiedDesktopEnabled_
: false,
137 * Whether the unified desktop option should be present.
140 showUnifiedDesktopOption_
: false,
143 * The current secondary display layout.
146 layout_
: options
.SecondaryDisplayLayout
.RIGHT
,
149 * The array of current output displays. It also contains the display
150 * rectangles currently rendered on screen.
151 * @type {Array<options.DisplayInfo>}
157 * The index for the currently focused display in the options UI. null if
164 * The primary display.
167 primaryDisplay_
: null,
170 * The secondary display.
173 secondaryDisplay_
: null,
176 * The container div element which contains all of the display rectangles.
182 * The scale factor of the actual display size to the drawn display
186 visualScale_
: VISUAL_SCALE
,
189 * The location where the last touch event happened. This is used to
190 * prevent unnecessary dragging events happen. Set to null unless it's
191 * during touch events.
194 lastTouchLocation_
: null,
197 * Whether the display settings can be shown.
203 initializePage: function() {
204 Page
.prototype.initializePage
.call(this);
206 $('display-options-select-mirroring').onchange
= (function() {
208 $('display-options-select-mirroring').value
== 'mirroring';
209 chrome
.send('setMirroring', [this.mirroring_
]);
212 var container
= $('display-options-displays-view-host');
213 container
.onmousemove
= this.onMouseMove_
.bind(this);
214 window
.addEventListener('mouseup', this.endDragging_
.bind(this), true);
215 container
.ontouchmove
= this.onTouchMove_
.bind(this);
216 container
.ontouchend
= this.endDragging_
.bind(this);
218 $('display-options-set-primary').onclick
= (function() {
219 chrome
.send('setPrimary', [this.displays_
[this.focusedIndex_
].id
]);
221 $('display-options-resolution-selection').onchange
= (function(ev
) {
222 var display
= this.displays_
[this.focusedIndex_
];
223 var resolution
= display
.resolutions
[ev
.target
.value
];
224 chrome
.send('setDisplayMode', [display
.id
, resolution
]);
226 $('display-options-orientation-selection').onchange
= (function(ev
) {
228 (this.focusedIndex_
=== null) ? 0 : this.focusedIndex_
;
229 chrome
.send('setOrientation', [this.displays_
[displayIndex
].id
,
232 $('display-options-color-profile-selection').onchange
= (function(ev
) {
233 chrome
.send('setColorProfile', [this.displays_
[this.focusedIndex_
].id
,
236 $('selected-display-start-calibrating-overscan').onclick
= (function() {
237 // Passes the target display ID. Do not specify it through URL hash,
238 // we do not care back/forward.
239 var displayOverscan
= options
.DisplayOverscan
.getInstance();
240 displayOverscan
.setDisplayId(this.displays_
[this.focusedIndex_
].id
);
241 PageManager
.showPageByName('displayOverscan');
242 chrome
.send('coreOptionsUserMetricsAction',
243 ['Options_DisplaySetOverscan']);
246 $('display-options-done').onclick = function() {
247 PageManager
.closeOverlay();
250 $('display-options-toggle-unified-desktop').onclick
= (function() {
251 this.unifiedDesktopEnabled_
= !this.unifiedDesktopEnabled_
;
252 chrome
.send('setUnifiedDesktopEnabled',
253 [this.unifiedDesktopEnabled_
]);
258 didShowPage: function() {
259 var optionTitles
= document
.getElementsByClassName(
260 'selected-display-option-title');
262 for (var i
= 0; i
< optionTitles
.length
; i
++)
263 maxSize
= Math
.max(maxSize
, optionTitles
[i
].clientWidth
);
264 for (var i
= 0; i
< optionTitles
.length
; i
++)
265 optionTitles
[i
].style
.width
= maxSize
+ 'px';
266 chrome
.send('getDisplayInfo');
270 canShowPage: function() {
271 return this.enabled_
;
275 * Enables or disables the page. When disabled, the page will not be able to
276 * open, and will close if currently opened.
277 * @param {boolean} enabled Whether the page should be enabled.
278 * @param {boolean} showUnifiedDesktop Whether the unified desktop option
281 setEnabled: function(enabled
, showUnifiedDesktop
) {
282 if (this.enabled_
== enabled
&&
283 this.showUnifiedDesktopOption_
== showUnifiedDesktop
) {
286 this.enabled_
= enabled
;
287 this.showUnifiedDesktopOption_
= showUnifiedDesktop
;
288 if (!enabled
&& this.visible
)
289 PageManager
.closeOverlay();
293 * Mouse move handler for dragging display rectangle.
294 * @param {Event} e The mouse move event.
297 onMouseMove_: function(e
) {
298 return this.processDragging_(e
, {x
: e
.pageX
, y
: e
.pageY
});
302 * Touch move handler for dragging display rectangle.
303 * @param {Event} e The touch move event.
306 onTouchMove_: function(e
) {
307 if (e
.touches
.length
!= 1)
310 var touchLocation
= {x
: e
.touches
[0].pageX
, y
: e
.touches
[0].pageY
};
311 // Touch move events happen even if the touch location doesn't change, but
312 // it doesn't need to process the dragging. Since sometimes the touch
313 // position changes slightly even though the user doesn't think to move
314 // the finger, very small move is just ignored.
315 /** @const */ var IGNORABLE_TOUCH_MOVE_PX
= 1;
316 var xDiff
= Math
.abs(touchLocation
.x
- this.lastTouchLocation_
.x
);
317 var yDiff
= Math
.abs(touchLocation
.y
- this.lastTouchLocation_
.y
);
318 if (xDiff
<= IGNORABLE_TOUCH_MOVE_PX
&&
319 yDiff
<= IGNORABLE_TOUCH_MOVE_PX
) {
323 this.lastTouchLocation_
= touchLocation
;
324 return this.processDragging_(e
, touchLocation
);
328 * Mouse down handler for dragging display rectangle.
329 * @param {Event} e The mouse down event.
332 onMouseDown_: function(e
) {
340 var target
= assertInstanceof(e
.target
, HTMLElement
);
341 return this.startDragging_(target
, {x
: e
.pageX
, y
: e
.pageY
});
345 * Touch start handler for dragging display rectangle.
346 * @param {Event} e The touch start event.
349 onTouchStart_: function(e
) {
353 if (e
.touches
.length
!= 1)
357 var touch
= e
.touches
[0];
358 this.lastTouchLocation_
= {x
: touch
.pageX
, y
: touch
.pageY
};
359 var target
= assertInstanceof(e
.target
, HTMLElement
);
360 return this.startDragging_(target
, this.lastTouchLocation_
);
364 * Collects the current data and sends it to Chrome.
367 applyResult_: function() {
368 // Offset is calculated from top or left edge.
369 var primary
= this.primaryDisplay_
;
370 var secondary
= this.secondaryDisplay_
;
372 if (this.layout_
== options
.SecondaryDisplayLayout
.LEFT
||
373 this.layout_
== options
.SecondaryDisplayLayout
.RIGHT
) {
374 offset
= secondary
.div
.offsetTop
- primary
.div
.offsetTop
;
376 offset
= secondary
.div
.offsetLeft
- primary
.div
.offsetLeft
;
378 chrome
.send('setDisplayLayout',
379 [this.layout_
, offset
/ this.visualScale_
]);
383 * Snaps the region [point, width] to [basePoint, baseWidth] if
384 * the [point, width] is close enough to the base's edge.
385 * @param {number} point The starting point of the region.
386 * @param {number} width The width of the region.
387 * @param {number} basePoint The starting point of the base region.
388 * @param {number} baseWidth The width of the base region.
389 * @return {number} The moved point. Returns point itself if it doesn't
390 * need to snap to the edge.
393 snapToEdge_: function(point
, width
, basePoint
, baseWidth
) {
394 // If the edge of the regions is smaller than this, it will snap to the
396 /** @const */ var SNAP_DISTANCE_PX
= 16;
398 var startDiff
= Math
.abs(point
- basePoint
);
399 var endDiff
= Math
.abs(point
+ width
- (basePoint
+ baseWidth
));
400 // Prefer the closer one if both edges are close enough.
401 if (startDiff
< SNAP_DISTANCE_PX
&& startDiff
< endDiff
)
403 else if (endDiff
< SNAP_DISTANCE_PX
)
404 return basePoint
+ baseWidth
- width
;
410 * Processes the actual dragging of display rectangle.
411 * @param {Event} e The event which triggers this drag.
412 * @param {Object} eventLocation The location where the event happens.
415 processDragging_: function(e
, eventLocation
) {
420 for (var i
= 0; i
< this.displays_
.length
; i
++) {
421 if (this.displays_
[i
] == this.dragging_
.display
) {
431 // Note that current code of moving display-rectangles doesn't work
432 // if there are >=3 displays. This is our assumption for M21.
433 // TODO(mukai): Fix the code to allow >=3 displays.
435 x
: this.dragging_
.originalLocation
.x
+
436 (eventLocation
.x
- this.dragging_
.eventLocation
.x
),
437 y
: this.dragging_
.originalLocation
.y
+
438 (eventLocation
.y
- this.dragging_
.eventLocation
.y
)
441 var baseDiv
= this.dragging_
.display
.isPrimary
?
442 this.secondaryDisplay_
.div
: this.primaryDisplay_
.div
;
443 var draggingDiv
= this.dragging_
.display
.div
;
445 newPosition
.x
= this.snapToEdge_(newPosition
.x
, draggingDiv
.offsetWidth
,
446 baseDiv
.offsetLeft
, baseDiv
.offsetWidth
);
447 newPosition
.y
= this.snapToEdge_(newPosition
.y
, draggingDiv
.offsetHeight
,
448 baseDiv
.offsetTop
, baseDiv
.offsetHeight
);
451 x
: newPosition
.x
+ draggingDiv
.offsetWidth
/ 2,
452 y
: newPosition
.y
+ draggingDiv
.offsetHeight
/ 2
456 x
: baseDiv
.offsetLeft
,
457 y
: baseDiv
.offsetTop
,
458 width
: baseDiv
.offsetWidth
,
459 height
: baseDiv
.offsetHeight
461 switch (getPositionToRectangle(baseBounds
, newCenter
)) {
462 case options
.SecondaryDisplayLayout
.RIGHT
:
463 this.layout_
= this.dragging_
.display
.isPrimary
?
464 options
.SecondaryDisplayLayout
.LEFT
:
465 options
.SecondaryDisplayLayout
.RIGHT
;
467 case options
.SecondaryDisplayLayout
.LEFT
:
468 this.layout_
= this.dragging_
.display
.isPrimary
?
469 options
.SecondaryDisplayLayout
.RIGHT
:
470 options
.SecondaryDisplayLayout
.LEFT
;
472 case options
.SecondaryDisplayLayout
.TOP
:
473 this.layout_
= this.dragging_
.display
.isPrimary
?
474 options
.SecondaryDisplayLayout
.BOTTOM
:
475 options
.SecondaryDisplayLayout
.TOP
;
477 case options
.SecondaryDisplayLayout
.BOTTOM
:
478 this.layout_
= this.dragging_
.display
.isPrimary
?
479 options
.SecondaryDisplayLayout
.TOP
:
480 options
.SecondaryDisplayLayout
.BOTTOM
;
484 if (this.layout_
== options
.SecondaryDisplayLayout
.LEFT
||
485 this.layout_
== options
.SecondaryDisplayLayout
.RIGHT
) {
486 if (newPosition
.y
> baseDiv
.offsetTop
+ baseDiv
.offsetHeight
)
487 this.layout_
= this.dragging_
.display
.isPrimary
?
488 options
.SecondaryDisplayLayout
.TOP
:
489 options
.SecondaryDisplayLayout
.BOTTOM
;
490 else if (newPosition
.y
+ draggingDiv
.offsetHeight
<
492 this.layout_
= this.dragging_
.display
.isPrimary
?
493 options
.SecondaryDisplayLayout
.BOTTOM
:
494 options
.SecondaryDisplayLayout
.TOP
;
496 if (newPosition
.x
> baseDiv
.offsetLeft
+ baseDiv
.offsetWidth
)
497 this.layout_
= this.dragging_
.display
.isPrimary
?
498 options
.SecondaryDisplayLayout
.LEFT
:
499 options
.SecondaryDisplayLayout
.RIGHT
;
500 else if (newPosition
.x
+ draggingDiv
.offsetWidth
<
502 this.layout_
= this.dragging_
.display
.isPrimary
?
503 options
.SecondaryDisplayLayout
.RIGHT
:
504 options
.SecondaryDisplayLayout
.LEFT
;
508 if (!this.dragging_
.display
.isPrimary
) {
509 layoutToBase
= this.layout_
;
511 switch (this.layout_
) {
512 case options
.SecondaryDisplayLayout
.RIGHT
:
513 layoutToBase
= options
.SecondaryDisplayLayout
.LEFT
;
515 case options
.SecondaryDisplayLayout
.LEFT
:
516 layoutToBase
= options
.SecondaryDisplayLayout
.RIGHT
;
518 case options
.SecondaryDisplayLayout
.TOP
:
519 layoutToBase
= options
.SecondaryDisplayLayout
.BOTTOM
;
521 case options
.SecondaryDisplayLayout
.BOTTOM
:
522 layoutToBase
= options
.SecondaryDisplayLayout
.TOP
;
527 switch (layoutToBase
) {
528 case options
.SecondaryDisplayLayout
.RIGHT
:
529 draggingDiv
.style
.left
=
530 baseDiv
.offsetLeft
+ baseDiv
.offsetWidth
+ 'px';
531 draggingDiv
.style
.top
= newPosition
.y
+ 'px';
533 case options
.SecondaryDisplayLayout
.LEFT
:
534 draggingDiv
.style
.left
=
535 baseDiv
.offsetLeft
- draggingDiv
.offsetWidth
+ 'px';
536 draggingDiv
.style
.top
= newPosition
.y
+ 'px';
538 case options
.SecondaryDisplayLayout
.TOP
:
539 draggingDiv
.style
.top
=
540 baseDiv
.offsetTop
- draggingDiv
.offsetHeight
+ 'px';
541 draggingDiv
.style
.left
= newPosition
.x
+ 'px';
543 case options
.SecondaryDisplayLayout
.BOTTOM
:
544 draggingDiv
.style
.top
=
545 baseDiv
.offsetTop
+ baseDiv
.offsetHeight
+ 'px';
546 draggingDiv
.style
.left
= newPosition
.x
+ 'px';
554 * start dragging of a display rectangle.
555 * @param {HTMLElement} target The event target.
556 * @param {Object} eventLocation The object to hold the location where
557 * this event happens.
560 startDragging_: function(target
, eventLocation
) {
561 this.focusedIndex_
= null;
562 for (var i
= 0; i
< this.displays_
.length
; i
++) {
563 var display
= this.displays_
[i
];
564 if (display
.div
== target
||
565 (target
.offsetParent
&& target
.offsetParent
== display
.div
)) {
566 this.focusedIndex_
= i
;
571 for (var i
= 0; i
< this.displays_
.length
; i
++) {
572 var display
= this.displays_
[i
];
573 display
.div
.className
= 'displays-display';
574 if (i
!= this.focusedIndex_
)
577 display
.div
.classList
.add('displays-focused');
578 if (this.displays_
.length
> 1) {
582 x
: display
.div
.offsetLeft
, y
: display
.div
.offsetTop
584 eventLocation
: eventLocation
589 this.updateSelectedDisplayDescription_();
594 * finish the current dragging of displays.
595 * @param {Event} e The event which triggers this.
598 endDragging_: function(e
) {
599 this.lastTouchLocation_
= null;
600 if (this.dragging_
) {
601 // Make sure the dragging location is connected.
602 var baseDiv
= this.dragging_
.display
.isPrimary
?
603 this.secondaryDisplay_
.div
: this.primaryDisplay_
.div
;
604 var draggingDiv
= this.dragging_
.display
.div
;
605 if (this.layout_
== options
.SecondaryDisplayLayout
.LEFT
||
606 this.layout_
== options
.SecondaryDisplayLayout
.RIGHT
) {
607 var top
= Math
.max(draggingDiv
.offsetTop
,
608 baseDiv
.offsetTop
- draggingDiv
.offsetHeight
+
611 baseDiv
.offsetTop
+ baseDiv
.offsetHeight
-
613 draggingDiv
.style
.top
= top
+ 'px';
615 var left
= Math
.max(draggingDiv
.offsetLeft
,
616 baseDiv
.offsetLeft
- draggingDiv
.offsetWidth
+
618 left
= Math
.min(left
,
619 baseDiv
.offsetLeft
+ baseDiv
.offsetWidth
-
621 draggingDiv
.style
.left
= left
+ 'px';
623 var originalPosition
= this.dragging_
.display
.originalPosition
;
624 if (originalPosition
.x
!= draggingDiv
.offsetLeft
||
625 originalPosition
.y
!= draggingDiv
.offsetTop
)
627 this.dragging_
= null;
629 this.updateSelectedDisplayDescription_();
634 * Updates the description of selected display section for mirroring mode.
637 updateSelectedDisplaySectionMirroring_: function() {
638 $('display-configuration-arrow').hidden
= true;
639 $('display-options-set-primary').disabled
= true;
640 $('display-options-select-mirroring').disabled
= false;
641 $('selected-display-start-calibrating-overscan').disabled
= true;
642 var display
= this.displays_
[0];
643 var orientation
= $('display-options-orientation-selection');
644 orientation
.disabled
= false;
645 var orientationOptions
= orientation
.getElementsByTagName('option');
646 orientationOptions
[display
.orientation
].selected
= true;
647 $('selected-display-name').textContent
=
648 loadTimeData
.getString('mirroringDisplay');
649 var resolution
= $('display-options-resolution-selection');
650 var option
= document
.createElement('option');
651 option
.value
= 'default';
652 option
.textContent
= display
.width
+ 'x' + display
.height
;
653 resolution
.appendChild(option
);
654 resolution
.disabled
= true;
658 * Updates the description of selected display section when no display is
662 updateSelectedDisplaySectionNoSelected_: function() {
663 $('display-configuration-arrow').hidden
= true;
664 $('display-options-set-primary').disabled
= true;
665 $('display-options-select-mirroring').disabled
= true;
666 $('selected-display-start-calibrating-overscan').disabled
= true;
667 $('display-options-orientation-selection').disabled
= true;
668 $('selected-display-name').textContent
= '';
669 var resolution
= $('display-options-resolution-selection');
670 resolution
.appendChild(document
.createElement('option'));
671 resolution
.disabled
= true;
675 * Updates the description of selected display section for the selected
677 * @param {Object} display The selected display object.
680 updateSelectedDisplaySectionForDisplay_: function(display
) {
681 var arrow
= $('display-configuration-arrow');
682 arrow
.hidden
= false;
683 // Adding 1 px to the position to fit the border line and the border in
685 arrow
.style
.top
= $('display-configurations').offsetTop
-
686 arrow
.offsetHeight
/ 2 + 'px';
687 arrow
.style
.left
= display
.div
.offsetLeft
+
688 display
.div
.offsetWidth
/ 2 - arrow
.offsetWidth
/ 2 + 'px';
690 $('display-options-set-primary').disabled
= display
.isPrimary
;
691 $('display-options-select-mirroring').disabled
=
692 (this.displays_
.length
<= 1 && !this.unifiedDesktopEnabled_
);
693 $('selected-display-start-calibrating-overscan').disabled
=
696 var orientation
= $('display-options-orientation-selection');
697 orientation
.disabled
= this.unifiedDesktopEnabled_
;
699 var orientationOptions
= orientation
.getElementsByTagName('option');
700 orientationOptions
[display
.orientation
].selected
= true;
702 $('selected-display-name').textContent
= display
.name
;
704 var resolution
= $('display-options-resolution-selection');
705 if (display
.resolutions
.length
<= 1) {
706 var option
= document
.createElement('option');
707 option
.value
= 'default';
708 option
.textContent
= display
.width
+ 'x' + display
.height
;
709 option
.selected
= true;
710 resolution
.appendChild(option
);
711 resolution
.disabled
= true;
714 for (var i
= 0; i
< display
.resolutions
.length
; i
++) {
715 var option
= document
.createElement('option');
717 option
.textContent
= display
.resolutions
[i
].width
+ 'x' +
718 display
.resolutions
[i
].height
;
719 if (display
.resolutions
[i
].isBest
) {
720 option
.textContent
+= ' ' +
721 loadTimeData
.getString('annotateBest');
722 } else if (display
.resolutions
[i
].isNative
) {
723 option
.textContent
+= ' ' +
724 loadTimeData
.getString('annotateNative');
726 if (display
.resolutions
[i
].deviceScaleFactor
&& previousOption
&&
727 previousOption
.textContent
== option
.textContent
) {
728 option
.textContent
+=
729 ' (' + display
.resolutions
[i
].deviceScaleFactor
+ 'x)';
731 option
.selected
= display
.resolutions
[i
].selected
;
732 resolution
.appendChild(option
);
733 previousOption
= option
;
735 resolution
.disabled
= (display
.resolutions
.length
<= 1);
738 if (display
.availableColorProfiles
.length
<= 1) {
739 $('selected-display-color-profile-row').hidden
= true;
741 $('selected-display-color-profile-row').hidden
= false;
742 var profiles
= $('display-options-color-profile-selection');
743 profiles
.innerHTML
= '';
744 for (var i
= 0; i
< display
.availableColorProfiles
.length
; i
++) {
745 var option
= document
.createElement('option');
746 var colorProfile
= display
.availableColorProfiles
[i
];
747 option
.value
= colorProfile
.profileId
;
748 option
.textContent
= colorProfile
.name
;
750 display
.colorProfile
== colorProfile
.profileId
);
751 profiles
.appendChild(option
);
757 * Updates the description of the selected display section.
760 updateSelectedDisplayDescription_: function() {
761 var resolution
= $('display-options-resolution-selection');
762 resolution
.textContent
= '';
763 var orientation
= $('display-options-orientation-selection');
764 var orientationOptions
= orientation
.getElementsByTagName('option');
765 for (var i
= 0; i
< orientationOptions
.length
; i
++)
766 orientationOptions
.selected
= false;
768 if (this.mirroring_
) {
769 this.updateSelectedDisplaySectionMirroring_();
770 } else if (this.focusedIndex_
== null ||
771 this.displays_
[this.focusedIndex_
] == null) {
772 this.updateSelectedDisplaySectionNoSelected_();
774 this.updateSelectedDisplaySectionForDisplay_(
775 this.displays_
[this.focusedIndex_
]);
780 * Clears the drawing area for display rectangles.
783 resetDisplaysView_: function() {
784 var displaysViewHost
= $('display-options-displays-view-host');
785 displaysViewHost
.removeChild(displaysViewHost
.firstChild
);
786 this.displaysView_
= document
.createElement('div');
787 this.displaysView_
.id
= 'display-options-displays-view';
788 displaysViewHost
.appendChild(this.displaysView_
);
792 * Lays out the display rectangles for mirroring.
795 layoutMirroringDisplays_: function() {
796 // Offset pixels for secondary display rectangles. The offset includes the
798 /** @const */ var MIRRORING_OFFSET_PIXELS
= 3;
799 // Always show two displays because there must be two displays when
800 // the display_options is enabled. Don't rely on displays_.length because
801 // there is only one display from chrome's perspective in mirror mode.
802 /** @const */ var MIN_NUM_DISPLAYS
= 2;
803 /** @const */ var MIRRORING_VERTICAL_MARGIN
= 20;
805 // The width/height should be same as the first display:
806 var width
= Math
.ceil(this.displays_
[0].width
* this.visualScale_
);
807 var height
= Math
.ceil(this.displays_
[0].height
* this.visualScale_
);
809 var numDisplays
= Math
.max(MIN_NUM_DISPLAYS
, this.displays_
.length
);
811 var totalWidth
= width
+ numDisplays
* MIRRORING_OFFSET_PIXELS
;
812 var totalHeight
= height
+ numDisplays
* MIRRORING_OFFSET_PIXELS
;
814 this.displaysView_
.style
.height
= totalHeight
+ 'px';
816 // The displays should be centered.
818 $('display-options-displays-view').offsetWidth
/ 2 - totalWidth
/ 2;
820 for (var i
= 0; i
< numDisplays
; i
++) {
821 var div
= document
.createElement('div');
822 div
.className
= 'displays-display';
823 div
.style
.top
= i
* MIRRORING_OFFSET_PIXELS
+ 'px';
824 div
.style
.left
= i
* MIRRORING_OFFSET_PIXELS
+ offsetX
+ 'px';
825 div
.style
.width
= width
+ 'px';
826 div
.style
.height
= height
+ 'px';
827 div
.style
.zIndex
= i
;
828 // set 'display-mirrored' class for the background display rectangles.
829 if (i
!= numDisplays
- 1)
830 div
.classList
.add('display-mirrored');
831 this.displaysView_
.appendChild(div
);
836 * Creates a div element representing the specified display.
837 * @param {Object} display The display object.
838 * @param {boolean} focused True if it's focused.
841 createDisplayRectangle_: function(display
, focused
) {
842 var div
= document
.createElement('div');
844 div
.className
= 'displays-display';
846 div
.classList
.add('displays-focused');
848 // div needs to be added to the DOM tree first, otherwise offsetHeight for
849 // nameContainer below cannot be computed.
850 this.displaysView_
.appendChild(div
);
852 var nameContainer
= document
.createElement('div');
853 nameContainer
.textContent
= display
.name
;
854 div
.appendChild(nameContainer
);
855 div
.style
.width
= Math
.floor(display
.width
* this.visualScale_
) + 'px';
856 var newHeight
= Math
.floor(display
.height
* this.visualScale_
);
857 div
.style
.height
= newHeight
+ 'px';
858 nameContainer
.style
.marginTop
=
859 (newHeight
- nameContainer
.offsetHeight
) / 2 + 'px';
861 div
.onmousedown
= this.onMouseDown_
.bind(this);
862 div
.ontouchstart
= this.onTouchStart_
.bind(this);
867 * Layouts the display rectangles according to the current layout_.
870 layoutDisplays_: function() {
873 var boundingBox
= {left
: 0, right
: 0, top
: 0, bottom
: 0};
874 this.primaryDisplay_
= null;
875 this.secondaryDisplay_
= null;
876 var focusedDisplay
= null;
877 for (var i
= 0; i
< this.displays_
.length
; i
++) {
878 var display
= this.displays_
[i
];
879 if (display
.isPrimary
)
880 this.primaryDisplay_
= display
;
882 this.secondaryDisplay_
= display
;
883 if (i
== this.focusedIndex_
)
884 focusedDisplay
= display
;
886 boundingBox
.left
= Math
.min(boundingBox
.left
, display
.x
);
887 boundingBox
.right
= Math
.max(
888 boundingBox
.right
, display
.x
+ display
.width
);
889 boundingBox
.top
= Math
.min(boundingBox
.top
, display
.y
);
890 boundingBox
.bottom
= Math
.max(
891 boundingBox
.bottom
, display
.y
+ display
.height
);
892 maxWidth
= Math
.max(maxWidth
, display
.width
);
893 maxHeight
= Math
.max(maxHeight
, display
.height
);
895 if (!this.primaryDisplay_
)
898 // Make the margin around the bounding box.
899 var areaWidth
= boundingBox
.right
- boundingBox
.left
+ maxWidth
;
900 var areaHeight
= boundingBox
.bottom
- boundingBox
.top
+ maxHeight
;
902 // Calculates the scale by the width since horizontal size is more strict.
903 // TODO(mukai): Adds the check of vertical size in case.
904 this.visualScale_
= Math
.min(
905 VISUAL_SCALE
, this.displaysView_
.offsetWidth
/ areaWidth
);
907 // Prepare enough area for thisplays_view by adding the maximum height.
908 this.displaysView_
.style
.height
=
909 Math
.ceil(areaHeight
* this.visualScale_
) + 'px';
911 // Centering the bounding box of the display rectangles.
913 x
: Math
.floor(this.displaysView_
.offsetWidth
/ 2 -
914 (boundingBox
.right
+ boundingBox
.left
) * this.visualScale_
/ 2),
915 y
: Math
.floor(this.displaysView_
.offsetHeight
/ 2 -
916 (boundingBox
.bottom
+ boundingBox
.top
) * this.visualScale_
/ 2)
919 // Layouting the display rectangles. First layout the primaryDisplay and
920 // then layout the secondary which is attaching to the primary.
921 var primaryDiv
= this.createDisplayRectangle_(
922 this.primaryDisplay_
, this.primaryDisplay_
== focusedDisplay
);
923 primaryDiv
.style
.left
=
924 Math
.floor(this.primaryDisplay_
.x
* this.visualScale_
) +
926 primaryDiv
.style
.top
=
927 Math
.floor(this.primaryDisplay_
.y
* this.visualScale_
) +
929 this.primaryDisplay_
.originalPosition
= {
930 x
: primaryDiv
.offsetLeft
, y
: primaryDiv
.offsetTop
};
932 if (this.secondaryDisplay_
) {
933 var secondaryDiv
= this.createDisplayRectangle_(
934 this.secondaryDisplay_
, this.secondaryDisplay_
== focusedDisplay
);
935 // Don't trust the secondary display's x or y, because it may cause a
936 // 1px gap due to rounding, which will create a fake update on end
937 // dragging. See crbug.com/386401
938 switch (this.layout_
) {
939 case options
.SecondaryDisplayLayout
.TOP
:
940 secondaryDiv
.style
.left
=
941 Math
.floor(this.secondaryDisplay_
.x
* this.visualScale_
) +
943 secondaryDiv
.style
.top
=
944 primaryDiv
.offsetTop
- secondaryDiv
.offsetHeight
+ 'px';
946 case options
.SecondaryDisplayLayout
.RIGHT
:
947 secondaryDiv
.style
.left
=
948 primaryDiv
.offsetLeft
+ primaryDiv
.offsetWidth
+ 'px';
949 secondaryDiv
.style
.top
=
950 Math
.floor(this.secondaryDisplay_
.y
* this.visualScale_
) +
953 case options
.SecondaryDisplayLayout
.BOTTOM
:
954 secondaryDiv
.style
.left
=
955 Math
.floor(this.secondaryDisplay_
.x
* this.visualScale_
) +
957 secondaryDiv
.style
.top
=
958 primaryDiv
.offsetTop
+ primaryDiv
.offsetHeight
+ 'px';
960 case options
.SecondaryDisplayLayout
.LEFT
:
961 secondaryDiv
.style
.left
=
962 primaryDiv
.offsetLeft
- secondaryDiv
.offsetWidth
+ 'px';
963 secondaryDiv
.style
.top
=
964 Math
.floor(this.secondaryDisplay_
.y
* this.visualScale_
) +
968 this.secondaryDisplay_
.originalPosition
= {
969 x
: secondaryDiv
.offsetLeft
, y
: secondaryDiv
.offsetTop
};
974 * Called when the display arrangement has changed.
975 * @param {options.MultiDisplayMode} mode multi display mode.
976 * @param {Array<options.DisplayInfo>} displays The list of the display
978 * @param {options.SecondaryDisplayLayout} layout The layout strategy.
979 * @param {number} offset The offset of the secondary display.
982 onDisplayChanged_: function(mode
, displays
, layout
, offset
) {
986 var hasExternal
= false;
987 for (var i
= 0; i
< displays
.length
; i
++) {
988 if (!displays
[i
].isInternal
) {
994 this.layout_
= layout
;
996 var mirroring
= mode
== options
.MultiDisplayMode
.MIRRORING
;
997 var unifiedDesktopEnabled
= mode
== options
.MultiDisplayMode
.UNIFIED
;
999 // Focus to the first display next to the primary one when |displays| list
1002 this.focusedIndex_
= null;
1003 } else if (this.mirroring_
!= mirroring
||
1004 this.unifiedDesktopEnabled_
!= unifiedDesktopEnabled
||
1005 this.displays_
.length
!= displays
.length
) {
1006 this.focusedIndex_
= 0;
1009 this.mirroring_
= mirroring
;
1010 this.unifiedDesktopEnabled_
= unifiedDesktopEnabled
;
1011 this.displays_
= displays
;
1013 this.resetDisplaysView_();
1014 if (this.mirroring_
)
1015 this.layoutMirroringDisplays_();
1017 this.layoutDisplays_();
1019 $('display-options-unified-desktop').hidden
=
1020 !this.showUnifiedDesktopOption_
;
1022 $('display-options-toggle-unified-desktop').checked
=
1023 this.unifiedDesktopEnabled_
;
1025 var disableUnifiedDesktopOption
=
1027 (!this.unifiedDesktopEnabled_
&&
1028 this.displays_
.length
== 1));
1030 $('display-options-toggle-unified-desktop').disabled
=
1031 disableUnifiedDesktopOption
;
1033 this.updateSelectedDisplayDescription_();
1037 DisplayOptions
.setDisplayInfo = function(
1038 mode
, displays
, layout
, offset
) {
1039 DisplayOptions
.getInstance().onDisplayChanged_(
1040 mode
, displays
, layout
, offset
);
1045 DisplayOptions
: DisplayOptions