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-toggle-mirroring').onclick
= (function() {
207 this.mirroring_
= !this.mirroring_
;
208 chrome
.send('setMirroring', [this.mirroring_
]);
211 var container
= $('display-options-displays-view-host');
212 container
.onmousemove
= this.onMouseMove_
.bind(this);
213 window
.addEventListener('mouseup', this.endDragging_
.bind(this), true);
214 container
.ontouchmove
= this.onTouchMove_
.bind(this);
215 container
.ontouchend
= this.endDragging_
.bind(this);
217 $('display-options-set-primary').onclick
= (function() {
218 chrome
.send('setPrimary', [this.displays_
[this.focusedIndex_
].id
]);
220 $('display-options-resolution-selection').onchange
= (function(ev
) {
221 var display
= this.displays_
[this.focusedIndex_
];
222 var resolution
= display
.resolutions
[ev
.target
.value
];
223 chrome
.send('setDisplayMode', [display
.id
, resolution
]);
225 $('display-options-orientation-selection').onchange
= (function(ev
) {
227 (this.focusedIndex_
=== null) ? 0 : this.focusedIndex_
;
228 chrome
.send('setOrientation', [this.displays_
[displayIndex
].id
,
231 $('display-options-color-profile-selection').onchange
= (function(ev
) {
232 chrome
.send('setColorProfile', [this.displays_
[this.focusedIndex_
].id
,
235 $('selected-display-start-calibrating-overscan').onclick
= (function() {
236 // Passes the target display ID. Do not specify it through URL hash,
237 // we do not care back/forward.
238 var displayOverscan
= options
.DisplayOverscan
.getInstance();
239 displayOverscan
.setDisplayId(this.displays_
[this.focusedIndex_
].id
);
240 PageManager
.showPageByName('displayOverscan');
241 chrome
.send('coreOptionsUserMetricsAction',
242 ['Options_DisplaySetOverscan']);
245 $('display-options-done').onclick = function() {
246 PageManager
.closeOverlay();
249 $('display-options-toggle-unified-desktop').onclick
= (function() {
250 this.unifiedDesktopEnabled_
= !this.unifiedDesktopEnabled_
;
251 chrome
.send('setUnifiedDesktopEnabled',
252 [this.unifiedDesktopEnabled_
]);
257 didShowPage: function() {
258 var optionTitles
= document
.getElementsByClassName(
259 'selected-display-option-title');
261 for (var i
= 0; i
< optionTitles
.length
; i
++)
262 maxSize
= Math
.max(maxSize
, optionTitles
[i
].clientWidth
);
263 for (var i
= 0; i
< optionTitles
.length
; i
++)
264 optionTitles
[i
].style
.width
= maxSize
+ 'px';
265 chrome
.send('getDisplayInfo');
269 canShowPage: function() {
270 return this.enabled_
;
274 * Enables or disables the page. When disabled, the page will not be able to
275 * open, and will close if currently opened.
276 * @param {boolean} enabled Whether the page should be enabled.
277 * @param {boolean} showUnifiedDesktop Whether the unified desktop option
280 setEnabled: function(enabled
, showUnifiedDesktop
) {
281 if (this.enabled_
== enabled
&&
282 this.showUnifiedDesktopOption_
== showUnifiedDesktop
) {
285 this.enabled_
= enabled
;
286 this.showUnifiedDesktopOption_
= showUnifiedDesktop
;
287 if (!enabled
&& this.visible
)
288 PageManager
.closeOverlay();
292 * Mouse move handler for dragging display rectangle.
293 * @param {Event} e The mouse move event.
296 onMouseMove_: function(e
) {
297 return this.processDragging_(e
, {x
: e
.pageX
, y
: e
.pageY
});
301 * Touch move handler for dragging display rectangle.
302 * @param {Event} e The touch move event.
305 onTouchMove_: function(e
) {
306 if (e
.touches
.length
!= 1)
309 var touchLocation
= {x
: e
.touches
[0].pageX
, y
: e
.touches
[0].pageY
};
310 // Touch move events happen even if the touch location doesn't change, but
311 // it doesn't need to process the dragging. Since sometimes the touch
312 // position changes slightly even though the user doesn't think to move
313 // the finger, very small move is just ignored.
314 /** @const */ var IGNORABLE_TOUCH_MOVE_PX
= 1;
315 var xDiff
= Math
.abs(touchLocation
.x
- this.lastTouchLocation_
.x
);
316 var yDiff
= Math
.abs(touchLocation
.y
- this.lastTouchLocation_
.y
);
317 if (xDiff
<= IGNORABLE_TOUCH_MOVE_PX
&&
318 yDiff
<= IGNORABLE_TOUCH_MOVE_PX
) {
322 this.lastTouchLocation_
= touchLocation
;
323 return this.processDragging_(e
, touchLocation
);
327 * Mouse down handler for dragging display rectangle.
328 * @param {Event} e The mouse down event.
331 onMouseDown_: function(e
) {
339 var target
= assertInstanceof(e
.target
, HTMLElement
);
340 return this.startDragging_(target
, {x
: e
.pageX
, y
: e
.pageY
});
344 * Touch start handler for dragging display rectangle.
345 * @param {Event} e The touch start event.
348 onTouchStart_: function(e
) {
352 if (e
.touches
.length
!= 1)
356 var touch
= e
.touches
[0];
357 this.lastTouchLocation_
= {x
: touch
.pageX
, y
: touch
.pageY
};
358 var target
= assertInstanceof(e
.target
, HTMLElement
);
359 return this.startDragging_(target
, this.lastTouchLocation_
);
363 * Collects the current data and sends it to Chrome.
366 applyResult_: function() {
367 // Offset is calculated from top or left edge.
368 var primary
= this.primaryDisplay_
;
369 var secondary
= this.secondaryDisplay_
;
371 if (this.layout_
== options
.SecondaryDisplayLayout
.LEFT
||
372 this.layout_
== options
.SecondaryDisplayLayout
.RIGHT
) {
373 offset
= secondary
.div
.offsetTop
- primary
.div
.offsetTop
;
375 offset
= secondary
.div
.offsetLeft
- primary
.div
.offsetLeft
;
377 chrome
.send('setDisplayLayout',
378 [this.layout_
, offset
/ this.visualScale_
]);
382 * Snaps the region [point, width] to [basePoint, baseWidth] if
383 * the [point, width] is close enough to the base's edge.
384 * @param {number} point The starting point of the region.
385 * @param {number} width The width of the region.
386 * @param {number} basePoint The starting point of the base region.
387 * @param {number} baseWidth The width of the base region.
388 * @return {number} The moved point. Returns point itself if it doesn't
389 * need to snap to the edge.
392 snapToEdge_: function(point
, width
, basePoint
, baseWidth
) {
393 // If the edge of the regions is smaller than this, it will snap to the
395 /** @const */ var SNAP_DISTANCE_PX
= 16;
397 var startDiff
= Math
.abs(point
- basePoint
);
398 var endDiff
= Math
.abs(point
+ width
- (basePoint
+ baseWidth
));
399 // Prefer the closer one if both edges are close enough.
400 if (startDiff
< SNAP_DISTANCE_PX
&& startDiff
< endDiff
)
402 else if (endDiff
< SNAP_DISTANCE_PX
)
403 return basePoint
+ baseWidth
- width
;
409 * Processes the actual dragging of display rectangle.
410 * @param {Event} e The event which triggers this drag.
411 * @param {Object} eventLocation The location where the event happens.
414 processDragging_: function(e
, eventLocation
) {
419 for (var i
= 0; i
< this.displays_
.length
; i
++) {
420 if (this.displays_
[i
] == this.dragging_
.display
) {
430 // Note that current code of moving display-rectangles doesn't work
431 // if there are >=3 displays. This is our assumption for M21.
432 // TODO(mukai): Fix the code to allow >=3 displays.
434 x
: this.dragging_
.originalLocation
.x
+
435 (eventLocation
.x
- this.dragging_
.eventLocation
.x
),
436 y
: this.dragging_
.originalLocation
.y
+
437 (eventLocation
.y
- this.dragging_
.eventLocation
.y
)
440 var baseDiv
= this.dragging_
.display
.isPrimary
?
441 this.secondaryDisplay_
.div
: this.primaryDisplay_
.div
;
442 var draggingDiv
= this.dragging_
.display
.div
;
444 newPosition
.x
= this.snapToEdge_(newPosition
.x
, draggingDiv
.offsetWidth
,
445 baseDiv
.offsetLeft
, baseDiv
.offsetWidth
);
446 newPosition
.y
= this.snapToEdge_(newPosition
.y
, draggingDiv
.offsetHeight
,
447 baseDiv
.offsetTop
, baseDiv
.offsetHeight
);
450 x
: newPosition
.x
+ draggingDiv
.offsetWidth
/ 2,
451 y
: newPosition
.y
+ draggingDiv
.offsetHeight
/ 2
455 x
: baseDiv
.offsetLeft
,
456 y
: baseDiv
.offsetTop
,
457 width
: baseDiv
.offsetWidth
,
458 height
: baseDiv
.offsetHeight
460 switch (getPositionToRectangle(baseBounds
, newCenter
)) {
461 case options
.SecondaryDisplayLayout
.RIGHT
:
462 this.layout_
= this.dragging_
.display
.isPrimary
?
463 options
.SecondaryDisplayLayout
.LEFT
:
464 options
.SecondaryDisplayLayout
.RIGHT
;
466 case options
.SecondaryDisplayLayout
.LEFT
:
467 this.layout_
= this.dragging_
.display
.isPrimary
?
468 options
.SecondaryDisplayLayout
.RIGHT
:
469 options
.SecondaryDisplayLayout
.LEFT
;
471 case options
.SecondaryDisplayLayout
.TOP
:
472 this.layout_
= this.dragging_
.display
.isPrimary
?
473 options
.SecondaryDisplayLayout
.BOTTOM
:
474 options
.SecondaryDisplayLayout
.TOP
;
476 case options
.SecondaryDisplayLayout
.BOTTOM
:
477 this.layout_
= this.dragging_
.display
.isPrimary
?
478 options
.SecondaryDisplayLayout
.TOP
:
479 options
.SecondaryDisplayLayout
.BOTTOM
;
483 if (this.layout_
== options
.SecondaryDisplayLayout
.LEFT
||
484 this.layout_
== options
.SecondaryDisplayLayout
.RIGHT
) {
485 if (newPosition
.y
> baseDiv
.offsetTop
+ baseDiv
.offsetHeight
)
486 this.layout_
= this.dragging_
.display
.isPrimary
?
487 options
.SecondaryDisplayLayout
.TOP
:
488 options
.SecondaryDisplayLayout
.BOTTOM
;
489 else if (newPosition
.y
+ draggingDiv
.offsetHeight
<
491 this.layout_
= this.dragging_
.display
.isPrimary
?
492 options
.SecondaryDisplayLayout
.BOTTOM
:
493 options
.SecondaryDisplayLayout
.TOP
;
495 if (newPosition
.x
> baseDiv
.offsetLeft
+ baseDiv
.offsetWidth
)
496 this.layout_
= this.dragging_
.display
.isPrimary
?
497 options
.SecondaryDisplayLayout
.LEFT
:
498 options
.SecondaryDisplayLayout
.RIGHT
;
499 else if (newPosition
.x
+ draggingDiv
.offsetWidth
<
501 this.layout_
= this.dragging_
.display
.isPrimary
?
502 options
.SecondaryDisplayLayout
.RIGHT
:
503 options
.SecondaryDisplayLayout
.LEFT
;
507 if (!this.dragging_
.display
.isPrimary
) {
508 layoutToBase
= this.layout_
;
510 switch (this.layout_
) {
511 case options
.SecondaryDisplayLayout
.RIGHT
:
512 layoutToBase
= options
.SecondaryDisplayLayout
.LEFT
;
514 case options
.SecondaryDisplayLayout
.LEFT
:
515 layoutToBase
= options
.SecondaryDisplayLayout
.RIGHT
;
517 case options
.SecondaryDisplayLayout
.TOP
:
518 layoutToBase
= options
.SecondaryDisplayLayout
.BOTTOM
;
520 case options
.SecondaryDisplayLayout
.BOTTOM
:
521 layoutToBase
= options
.SecondaryDisplayLayout
.TOP
;
526 switch (layoutToBase
) {
527 case options
.SecondaryDisplayLayout
.RIGHT
:
528 draggingDiv
.style
.left
=
529 baseDiv
.offsetLeft
+ baseDiv
.offsetWidth
+ 'px';
530 draggingDiv
.style
.top
= newPosition
.y
+ 'px';
532 case options
.SecondaryDisplayLayout
.LEFT
:
533 draggingDiv
.style
.left
=
534 baseDiv
.offsetLeft
- draggingDiv
.offsetWidth
+ 'px';
535 draggingDiv
.style
.top
= newPosition
.y
+ 'px';
537 case options
.SecondaryDisplayLayout
.TOP
:
538 draggingDiv
.style
.top
=
539 baseDiv
.offsetTop
- draggingDiv
.offsetHeight
+ 'px';
540 draggingDiv
.style
.left
= newPosition
.x
+ 'px';
542 case options
.SecondaryDisplayLayout
.BOTTOM
:
543 draggingDiv
.style
.top
=
544 baseDiv
.offsetTop
+ baseDiv
.offsetHeight
+ 'px';
545 draggingDiv
.style
.left
= newPosition
.x
+ 'px';
553 * start dragging of a display rectangle.
554 * @param {HTMLElement} target The event target.
555 * @param {Object} eventLocation The object to hold the location where
556 * this event happens.
559 startDragging_: function(target
, eventLocation
) {
560 this.focusedIndex_
= null;
561 for (var i
= 0; i
< this.displays_
.length
; i
++) {
562 var display
= this.displays_
[i
];
563 if (display
.div
== target
||
564 (target
.offsetParent
&& target
.offsetParent
== display
.div
)) {
565 this.focusedIndex_
= i
;
570 for (var i
= 0; i
< this.displays_
.length
; i
++) {
571 var display
= this.displays_
[i
];
572 display
.div
.className
= 'displays-display';
573 if (i
!= this.focusedIndex_
)
576 display
.div
.classList
.add('displays-focused');
577 if (this.displays_
.length
> 1) {
581 x
: display
.div
.offsetLeft
, y
: display
.div
.offsetTop
583 eventLocation
: eventLocation
588 this.updateSelectedDisplayDescription_();
593 * finish the current dragging of displays.
594 * @param {Event} e The event which triggers this.
597 endDragging_: function(e
) {
598 this.lastTouchLocation_
= null;
599 if (this.dragging_
) {
600 // Make sure the dragging location is connected.
601 var baseDiv
= this.dragging_
.display
.isPrimary
?
602 this.secondaryDisplay_
.div
: this.primaryDisplay_
.div
;
603 var draggingDiv
= this.dragging_
.display
.div
;
604 if (this.layout_
== options
.SecondaryDisplayLayout
.LEFT
||
605 this.layout_
== options
.SecondaryDisplayLayout
.RIGHT
) {
606 var top
= Math
.max(draggingDiv
.offsetTop
,
607 baseDiv
.offsetTop
- draggingDiv
.offsetHeight
+
610 baseDiv
.offsetTop
+ baseDiv
.offsetHeight
-
612 draggingDiv
.style
.top
= top
+ 'px';
614 var left
= Math
.max(draggingDiv
.offsetLeft
,
615 baseDiv
.offsetLeft
- draggingDiv
.offsetWidth
+
617 left
= Math
.min(left
,
618 baseDiv
.offsetLeft
+ baseDiv
.offsetWidth
-
620 draggingDiv
.style
.left
= left
+ 'px';
622 var originalPosition
= this.dragging_
.display
.originalPosition
;
623 if (originalPosition
.x
!= draggingDiv
.offsetLeft
||
624 originalPosition
.y
!= draggingDiv
.offsetTop
)
626 this.dragging_
= null;
628 this.updateSelectedDisplayDescription_();
633 * Updates the description of selected display section for mirroring mode.
636 updateSelectedDisplaySectionMirroring_: function() {
637 $('display-configuration-arrow').hidden
= true;
638 $('display-options-set-primary').disabled
= true;
639 $('display-options-toggle-mirroring').disabled
= false;
640 $('selected-display-start-calibrating-overscan').disabled
= true;
641 var display
= this.displays_
[0];
642 var orientation
= $('display-options-orientation-selection');
643 orientation
.disabled
= false;
644 var orientationOptions
= orientation
.getElementsByTagName('option');
645 orientationOptions
[display
.orientation
].selected
= true;
646 $('selected-display-name').textContent
=
647 loadTimeData
.getString('mirroringDisplay');
648 var resolution
= $('display-options-resolution-selection');
649 var option
= document
.createElement('option');
650 option
.value
= 'default';
651 option
.textContent
= display
.width
+ 'x' + display
.height
;
652 resolution
.appendChild(option
);
653 resolution
.disabled
= true;
657 * Updates the description of selected display section when no display is
661 updateSelectedDisplaySectionNoSelected_: function() {
662 $('display-configuration-arrow').hidden
= true;
663 $('display-options-set-primary').disabled
= true;
664 $('display-options-toggle-mirroring').disabled
= true;
665 $('selected-display-start-calibrating-overscan').disabled
= true;
666 $('display-options-orientation-selection').disabled
= true;
667 $('selected-display-name').textContent
= '';
668 var resolution
= $('display-options-resolution-selection');
669 resolution
.appendChild(document
.createElement('option'));
670 resolution
.disabled
= true;
674 * Updates the description of selected display section for the selected
676 * @param {Object} display The selected display object.
679 updateSelectedDisplaySectionForDisplay_: function(display
) {
680 var arrow
= $('display-configuration-arrow');
681 arrow
.hidden
= false;
682 // Adding 1 px to the position to fit the border line and the border in
684 arrow
.style
.top
= $('display-configurations').offsetTop
-
685 arrow
.offsetHeight
/ 2 + 'px';
686 arrow
.style
.left
= display
.div
.offsetLeft
+
687 display
.div
.offsetWidth
/ 2 - arrow
.offsetWidth
/ 2 + 'px';
689 $('display-options-set-primary').disabled
= display
.isPrimary
;
690 $('display-options-toggle-mirroring').disabled
=
691 (this.displays_
.length
<= 1);
692 $('selected-display-start-calibrating-overscan').disabled
=
695 var orientation
= $('display-options-orientation-selection');
696 orientation
.disabled
= this.unifiedDesktopEnabled_
;
698 var orientationOptions
= orientation
.getElementsByTagName('option');
699 orientationOptions
[display
.orientation
].selected
= true;
701 $('selected-display-name').textContent
= display
.name
;
703 var resolution
= $('display-options-resolution-selection');
704 if (display
.resolutions
.length
<= 1) {
705 var option
= document
.createElement('option');
706 option
.value
= 'default';
707 option
.textContent
= display
.width
+ 'x' + display
.height
;
708 option
.selected
= true;
709 resolution
.appendChild(option
);
710 resolution
.disabled
= true;
713 for (var i
= 0; i
< display
.resolutions
.length
; i
++) {
714 var option
= document
.createElement('option');
716 option
.textContent
= display
.resolutions
[i
].width
+ 'x' +
717 display
.resolutions
[i
].height
;
718 if (display
.resolutions
[i
].isBest
) {
719 option
.textContent
+= ' ' +
720 loadTimeData
.getString('annotateBest');
721 } else if (display
.resolutions
[i
].isNative
) {
722 option
.textContent
+= ' ' +
723 loadTimeData
.getString('annotateNative');
725 if (display
.resolutions
[i
].deviceScaleFactor
&& previousOption
&&
726 previousOption
.textContent
== option
.textContent
) {
727 option
.textContent
+=
728 ' (' + display
.resolutions
[i
].deviceScaleFactor
+ 'x)';
730 option
.selected
= display
.resolutions
[i
].selected
;
731 resolution
.appendChild(option
);
732 previousOption
= option
;
734 resolution
.disabled
= (display
.resolutions
.length
<= 1);
737 if (display
.availableColorProfiles
.length
<= 1) {
738 $('selected-display-color-profile-row').hidden
= true;
740 $('selected-display-color-profile-row').hidden
= false;
741 var profiles
= $('display-options-color-profile-selection');
742 profiles
.innerHTML
= '';
743 for (var i
= 0; i
< display
.availableColorProfiles
.length
; i
++) {
744 var option
= document
.createElement('option');
745 var colorProfile
= display
.availableColorProfiles
[i
];
746 option
.value
= colorProfile
.profileId
;
747 option
.textContent
= colorProfile
.name
;
749 display
.colorProfile
== colorProfile
.profileId
);
750 profiles
.appendChild(option
);
756 * Updates the description of the selected display section.
759 updateSelectedDisplayDescription_: function() {
760 var resolution
= $('display-options-resolution-selection');
761 resolution
.textContent
= '';
762 var orientation
= $('display-options-orientation-selection');
763 var orientationOptions
= orientation
.getElementsByTagName('option');
764 for (var i
= 0; i
< orientationOptions
.length
; i
++)
765 orientationOptions
.selected
= false;
767 if (this.mirroring_
) {
768 this.updateSelectedDisplaySectionMirroring_();
769 } else if (this.focusedIndex_
== null ||
770 this.displays_
[this.focusedIndex_
] == null) {
771 this.updateSelectedDisplaySectionNoSelected_();
773 this.updateSelectedDisplaySectionForDisplay_(
774 this.displays_
[this.focusedIndex_
]);
779 * Clears the drawing area for display rectangles.
782 resetDisplaysView_: function() {
783 var displaysViewHost
= $('display-options-displays-view-host');
784 displaysViewHost
.removeChild(displaysViewHost
.firstChild
);
785 this.displaysView_
= document
.createElement('div');
786 this.displaysView_
.id
= 'display-options-displays-view';
787 displaysViewHost
.appendChild(this.displaysView_
);
791 * Lays out the display rectangles for mirroring.
794 layoutMirroringDisplays_: function() {
795 // Offset pixels for secondary display rectangles. The offset includes the
797 /** @const */ var MIRRORING_OFFSET_PIXELS
= 3;
798 // Always show two displays because there must be two displays when
799 // the display_options is enabled. Don't rely on displays_.length because
800 // there is only one display from chrome's perspective in mirror mode.
801 /** @const */ var MIN_NUM_DISPLAYS
= 2;
802 /** @const */ var MIRRORING_VERTICAL_MARGIN
= 20;
804 // The width/height should be same as the first display:
805 var width
= Math
.ceil(this.displays_
[0].width
* this.visualScale_
);
806 var height
= Math
.ceil(this.displays_
[0].height
* this.visualScale_
);
808 var numDisplays
= Math
.max(MIN_NUM_DISPLAYS
, this.displays_
.length
);
810 var totalWidth
= width
+ numDisplays
* MIRRORING_OFFSET_PIXELS
;
811 var totalHeight
= height
+ numDisplays
* MIRRORING_OFFSET_PIXELS
;
813 this.displaysView_
.style
.height
= totalHeight
+ 'px';
815 // The displays should be centered.
817 $('display-options-displays-view').offsetWidth
/ 2 - totalWidth
/ 2;
819 for (var i
= 0; i
< numDisplays
; i
++) {
820 var div
= document
.createElement('div');
821 div
.className
= 'displays-display';
822 div
.style
.top
= i
* MIRRORING_OFFSET_PIXELS
+ 'px';
823 div
.style
.left
= i
* MIRRORING_OFFSET_PIXELS
+ offsetX
+ 'px';
824 div
.style
.width
= width
+ 'px';
825 div
.style
.height
= height
+ 'px';
826 div
.style
.zIndex
= i
;
827 // set 'display-mirrored' class for the background display rectangles.
828 if (i
!= numDisplays
- 1)
829 div
.classList
.add('display-mirrored');
830 this.displaysView_
.appendChild(div
);
835 * Creates a div element representing the specified display.
836 * @param {Object} display The display object.
837 * @param {boolean} focused True if it's focused.
840 createDisplayRectangle_: function(display
, focused
) {
841 var div
= document
.createElement('div');
843 div
.className
= 'displays-display';
845 div
.classList
.add('displays-focused');
847 // div needs to be added to the DOM tree first, otherwise offsetHeight for
848 // nameContainer below cannot be computed.
849 this.displaysView_
.appendChild(div
);
851 var nameContainer
= document
.createElement('div');
852 nameContainer
.textContent
= display
.name
;
853 div
.appendChild(nameContainer
);
854 div
.style
.width
= Math
.floor(display
.width
* this.visualScale_
) + 'px';
855 var newHeight
= Math
.floor(display
.height
* this.visualScale_
);
856 div
.style
.height
= newHeight
+ 'px';
857 nameContainer
.style
.marginTop
=
858 (newHeight
- nameContainer
.offsetHeight
) / 2 + 'px';
860 div
.onmousedown
= this.onMouseDown_
.bind(this);
861 div
.ontouchstart
= this.onTouchStart_
.bind(this);
866 * Layouts the display rectangles according to the current layout_.
869 layoutDisplays_: function() {
872 var boundingBox
= {left
: 0, right
: 0, top
: 0, bottom
: 0};
873 this.primaryDisplay_
= null;
874 this.secondaryDisplay_
= null;
875 var focusedDisplay
= null;
876 for (var i
= 0; i
< this.displays_
.length
; i
++) {
877 var display
= this.displays_
[i
];
878 if (display
.isPrimary
)
879 this.primaryDisplay_
= display
;
881 this.secondaryDisplay_
= display
;
882 if (i
== this.focusedIndex_
)
883 focusedDisplay
= display
;
885 boundingBox
.left
= Math
.min(boundingBox
.left
, display
.x
);
886 boundingBox
.right
= Math
.max(
887 boundingBox
.right
, display
.x
+ display
.width
);
888 boundingBox
.top
= Math
.min(boundingBox
.top
, display
.y
);
889 boundingBox
.bottom
= Math
.max(
890 boundingBox
.bottom
, display
.y
+ display
.height
);
891 maxWidth
= Math
.max(maxWidth
, display
.width
);
892 maxHeight
= Math
.max(maxHeight
, display
.height
);
894 if (!this.primaryDisplay_
)
897 // Make the margin around the bounding box.
898 var areaWidth
= boundingBox
.right
- boundingBox
.left
+ maxWidth
;
899 var areaHeight
= boundingBox
.bottom
- boundingBox
.top
+ maxHeight
;
901 // Calculates the scale by the width since horizontal size is more strict.
902 // TODO(mukai): Adds the check of vertical size in case.
903 this.visualScale_
= Math
.min(
904 VISUAL_SCALE
, this.displaysView_
.offsetWidth
/ areaWidth
);
906 // Prepare enough area for thisplays_view by adding the maximum height.
907 this.displaysView_
.style
.height
=
908 Math
.ceil(areaHeight
* this.visualScale_
) + 'px';
910 // Centering the bounding box of the display rectangles.
912 x
: Math
.floor(this.displaysView_
.offsetWidth
/ 2 -
913 (boundingBox
.right
+ boundingBox
.left
) * this.visualScale_
/ 2),
914 y
: Math
.floor(this.displaysView_
.offsetHeight
/ 2 -
915 (boundingBox
.bottom
+ boundingBox
.top
) * this.visualScale_
/ 2)
918 // Layouting the display rectangles. First layout the primaryDisplay and
919 // then layout the secondary which is attaching to the primary.
920 var primaryDiv
= this.createDisplayRectangle_(
921 this.primaryDisplay_
, this.primaryDisplay_
== focusedDisplay
);
922 primaryDiv
.style
.left
=
923 Math
.floor(this.primaryDisplay_
.x
* this.visualScale_
) +
925 primaryDiv
.style
.top
=
926 Math
.floor(this.primaryDisplay_
.y
* this.visualScale_
) +
928 this.primaryDisplay_
.originalPosition
= {
929 x
: primaryDiv
.offsetLeft
, y
: primaryDiv
.offsetTop
};
931 if (this.secondaryDisplay_
) {
932 var secondaryDiv
= this.createDisplayRectangle_(
933 this.secondaryDisplay_
, this.secondaryDisplay_
== focusedDisplay
);
934 // Don't trust the secondary display's x or y, because it may cause a
935 // 1px gap due to rounding, which will create a fake update on end
936 // dragging. See crbug.com/386401
937 switch (this.layout_
) {
938 case options
.SecondaryDisplayLayout
.TOP
:
939 secondaryDiv
.style
.left
=
940 Math
.floor(this.secondaryDisplay_
.x
* this.visualScale_
) +
942 secondaryDiv
.style
.top
=
943 primaryDiv
.offsetTop
- secondaryDiv
.offsetHeight
+ 'px';
945 case options
.SecondaryDisplayLayout
.RIGHT
:
946 secondaryDiv
.style
.left
=
947 primaryDiv
.offsetLeft
+ primaryDiv
.offsetWidth
+ 'px';
948 secondaryDiv
.style
.top
=
949 Math
.floor(this.secondaryDisplay_
.y
* this.visualScale_
) +
952 case options
.SecondaryDisplayLayout
.BOTTOM
:
953 secondaryDiv
.style
.left
=
954 Math
.floor(this.secondaryDisplay_
.x
* this.visualScale_
) +
956 secondaryDiv
.style
.top
=
957 primaryDiv
.offsetTop
+ primaryDiv
.offsetHeight
+ 'px';
959 case options
.SecondaryDisplayLayout
.LEFT
:
960 secondaryDiv
.style
.left
=
961 primaryDiv
.offsetLeft
- secondaryDiv
.offsetWidth
+ 'px';
962 secondaryDiv
.style
.top
=
963 Math
.floor(this.secondaryDisplay_
.y
* this.visualScale_
) +
967 this.secondaryDisplay_
.originalPosition
= {
968 x
: secondaryDiv
.offsetLeft
, y
: secondaryDiv
.offsetTop
};
973 * Called when the display arrangement has changed.
974 * @param {options.MultiDisplayMode} mode multi display mode.
975 * @param {Array<options.DisplayInfo>} displays The list of the display
977 * @param {options.SecondaryDisplayLayout} layout The layout strategy.
978 * @param {number} offset The offset of the secondary display.
981 onDisplayChanged_: function(mode
, displays
, layout
, offset
) {
985 var hasExternal
= false;
986 for (var i
= 0; i
< displays
.length
; i
++) {
987 if (!displays
[i
].isInternal
) {
993 this.layout_
= layout
;
995 var mirroring
= mode
== options
.MultiDisplayMode
.MIRRORING
;
996 var unifiedDesktopEnabled
= mode
== options
.MultiDisplayMode
.UNIFIED
;
998 $('display-options-toggle-mirroring').textContent
=
999 loadTimeData
.getString(
1000 mirroring
? 'stopMirroring' : 'startMirroring');
1002 // Focus to the first display next to the primary one when |displays| list
1005 this.focusedIndex_
= null;
1006 } else if (this.mirroring_
!= mirroring
||
1007 this.unifiedDesktopEnabled_
!= unifiedDesktopEnabled
||
1008 this.displays_
.length
!= displays
.length
) {
1009 this.focusedIndex_
= 0;
1012 this.mirroring_
= mirroring
;
1013 this.unifiedDesktopEnabled_
= unifiedDesktopEnabled
;
1014 this.displays_
= displays
;
1016 this.resetDisplaysView_();
1017 if (this.mirroring_
)
1018 this.layoutMirroringDisplays_();
1020 this.layoutDisplays_();
1022 $('display-options-unified-desktop').hidden
=
1023 !this.showUnifiedDesktopOption_
;
1025 $('display-options-toggle-unified-desktop').checked
=
1026 this.unifiedDesktopEnabled_
;
1028 var disableUnifiedDesktopOption
=
1030 (!this.unifiedDesktopEnabled_
&&
1031 this.displays_
.length
== 1));
1033 $('display-options-toggle-unified-desktop').disabled
=
1034 disableUnifiedDesktopOption
;
1036 this.updateSelectedDisplayDescription_();
1040 DisplayOptions
.setDisplayInfo = function(
1041 mode
, displays
, layout
, offset
) {
1042 DisplayOptions
.getInstance().onDisplayChanged_(
1043 mode
, displays
, layout
, offset
);
1048 DisplayOptions
: DisplayOptions