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
= {
39 cr
.define('options', function() {
40 var Page
= cr
.ui
.pageManager
.Page
;
41 var PageManager
= cr
.ui
.pageManager
.PageManager
;
43 // The scale ratio of the display rectangle to its original size.
44 /** @const */ var VISUAL_SCALE
= 1 / 10;
46 // The number of pixels to share the edges between displays.
47 /** @const */ var MIN_OFFSET_OVERLAP
= 5;
50 * Calculates the bounds of |element| relative to the page.
51 * @param {HTMLElement} element The element to be known.
52 * @return {Object} The object for the bounds, with x, y, width, and height.
54 function getBoundsInPage(element
) {
56 x
: element
.offsetLeft
,
58 width
: element
.offsetWidth
,
59 height
: element
.offsetHeight
61 var parent
= element
.offsetParent
;
62 while (parent
&& parent
!= document
.body
) {
63 bounds
.x
+= parent
.offsetLeft
;
64 bounds
.y
+= parent
.offsetTop
;
65 parent
= parent
.offsetParent
;
71 * Gets the position of |point| to |rect|, left, right, top, or bottom.
72 * @param {Object} rect The base rectangle with x, y, width, and height.
73 * @param {Object} point The point to check the position.
74 * @return {options.SecondaryDisplayLayout} The position of the calculated
77 function getPositionToRectangle(rect
, point
) {
78 // Separates the area into four (LEFT/RIGHT/TOP/BOTTOM) by the diagonals of
79 // the rect, and decides which area the display should reside.
80 var diagonalSlope
= rect
.height
/ rect
.width
;
81 var topDownIntercept
= rect
.y
- rect
.x
* diagonalSlope
;
82 var bottomUpIntercept
= rect
.y
+ rect
.height
+ rect
.x
* diagonalSlope
;
84 if (point
.y
> topDownIntercept
+ point
.x
* diagonalSlope
) {
85 if (point
.y
> bottomUpIntercept
- point
.x
* diagonalSlope
)
86 return options
.SecondaryDisplayLayout
.BOTTOM
;
88 return options
.SecondaryDisplayLayout
.LEFT
;
90 if (point
.y
> bottomUpIntercept
- point
.x
* diagonalSlope
)
91 return options
.SecondaryDisplayLayout
.RIGHT
;
93 return options
.SecondaryDisplayLayout
.TOP
;
98 * Encapsulated handling of the 'Display' page.
100 * @extends {cr.ui.pageManager.Page}
102 function DisplayOptions() {
103 Page
.call(this, 'display',
104 loadTimeData
.getString('displayOptionsPageTabTitle'),
105 'display-options-page');
108 cr
.addSingletonGetter(DisplayOptions
);
110 DisplayOptions
.prototype = {
111 __proto__
: Page
.prototype,
114 * Whether the current output status is mirroring displays or not.
120 * The current secondary display layout.
123 layout_
: options
.SecondaryDisplayLayout
.RIGHT
,
126 * The array of current output displays. It also contains the display
127 * rectangles currently rendered on screen.
128 * @type {Array<options.DisplayInfo>}
134 * The index for the currently focused display in the options UI. null if
141 * The primary display.
144 primaryDisplay_
: null,
147 * The secondary display.
150 secondaryDisplay_
: null,
153 * The container div element which contains all of the display rectangles.
159 * The scale factor of the actual display size to the drawn display
163 visualScale_
: VISUAL_SCALE
,
166 * The location where the last touch event happened. This is used to
167 * prevent unnecessary dragging events happen. Set to null unless it's
168 * during touch events.
171 lastTouchLocation_
: null,
174 * Whether the display settings can be shown.
180 initializePage: function() {
181 Page
.prototype.initializePage
.call(this);
183 $('display-options-toggle-mirroring').onclick
= (function() {
184 this.mirroring_
= !this.mirroring_
;
185 chrome
.send('setMirroring', [this.mirroring_
]);
188 var container
= $('display-options-displays-view-host');
189 container
.onmousemove
= this.onMouseMove_
.bind(this);
190 window
.addEventListener('mouseup', this.endDragging_
.bind(this), true);
191 container
.ontouchmove
= this.onTouchMove_
.bind(this);
192 container
.ontouchend
= this.endDragging_
.bind(this);
194 $('display-options-set-primary').onclick
= (function() {
195 chrome
.send('setPrimary', [this.displays_
[this.focusedIndex_
].id
]);
197 $('display-options-resolution-selection').onchange
= (function(ev
) {
198 var display
= this.displays_
[this.focusedIndex_
];
199 var resolution
= display
.resolutions
[ev
.target
.value
];
200 chrome
.send('setDisplayMode', [display
.id
, resolution
]);
202 $('display-options-orientation-selection').onchange
= (function(ev
) {
203 chrome
.send('setOrientation', [this.displays_
[this.focusedIndex_
].id
,
206 $('display-options-color-profile-selection').onchange
= (function(ev
) {
207 chrome
.send('setColorProfile', [this.displays_
[this.focusedIndex_
].id
,
210 $('selected-display-start-calibrating-overscan').onclick
= (function() {
211 // Passes the target display ID. Do not specify it through URL hash,
212 // we do not care back/forward.
213 var displayOverscan
= options
.DisplayOverscan
.getInstance();
214 displayOverscan
.setDisplayId(this.displays_
[this.focusedIndex_
].id
);
215 PageManager
.showPageByName('displayOverscan');
216 chrome
.send('coreOptionsUserMetricsAction',
217 ['Options_DisplaySetOverscan']);
220 $('display-options-done').onclick = function() {
221 PageManager
.closeOverlay();
226 didShowPage: function() {
227 var optionTitles
= document
.getElementsByClassName(
228 'selected-display-option-title');
230 for (var i
= 0; i
< optionTitles
.length
; i
++)
231 maxSize
= Math
.max(maxSize
, optionTitles
[i
].clientWidth
);
232 for (var i
= 0; i
< optionTitles
.length
; i
++)
233 optionTitles
[i
].style
.width
= maxSize
+ 'px';
234 chrome
.send('getDisplayInfo');
238 canShowPage: function() {
239 return this.enabled_
;
243 * Enables or disables the page. When disabled, the page will not be able to
244 * open, and will close if currently opened.
245 * @param {boolean} enabled Whether the page should be enabled.
247 setEnabled: function(enabled
) {
248 if (this.enabled_
== enabled
)
250 this.enabled_
= enabled
;
251 if (!enabled
&& this.visible
)
252 PageManager
.closeOverlay();
256 * Mouse move handler for dragging display rectangle.
257 * @param {Event} e The mouse move event.
260 onMouseMove_: function(e
) {
261 return this.processDragging_(e
, {x
: e
.pageX
, y
: e
.pageY
});
265 * Touch move handler for dragging display rectangle.
266 * @param {Event} e The touch move event.
269 onTouchMove_: function(e
) {
270 if (e
.touches
.length
!= 1)
273 var touchLocation
= {x
: e
.touches
[0].pageX
, y
: e
.touches
[0].pageY
};
274 // Touch move events happen even if the touch location doesn't change, but
275 // it doesn't need to process the dragging. Since sometimes the touch
276 // position changes slightly even though the user doesn't think to move
277 // the finger, very small move is just ignored.
278 /** @const */ var IGNORABLE_TOUCH_MOVE_PX
= 1;
279 var xDiff
= Math
.abs(touchLocation
.x
- this.lastTouchLocation_
.x
);
280 var yDiff
= Math
.abs(touchLocation
.y
- this.lastTouchLocation_
.y
);
281 if (xDiff
<= IGNORABLE_TOUCH_MOVE_PX
&&
282 yDiff
<= IGNORABLE_TOUCH_MOVE_PX
) {
286 this.lastTouchLocation_
= touchLocation
;
287 return this.processDragging_(e
, touchLocation
);
291 * Mouse down handler for dragging display rectangle.
292 * @param {Event} e The mouse down event.
295 onMouseDown_: function(e
) {
303 var target
= assertInstanceof(e
.target
, HTMLElement
);
304 return this.startDragging_(target
, {x
: e
.pageX
, y
: e
.pageY
});
308 * Touch start handler for dragging display rectangle.
309 * @param {Event} e The touch start event.
312 onTouchStart_: function(e
) {
316 if (e
.touches
.length
!= 1)
320 var touch
= e
.touches
[0];
321 this.lastTouchLocation_
= {x
: touch
.pageX
, y
: touch
.pageY
};
322 var target
= assertInstanceof(e
.target
, HTMLElement
);
323 return this.startDragging_(target
, this.lastTouchLocation_
);
327 * Collects the current data and sends it to Chrome.
330 applyResult_: function() {
331 // Offset is calculated from top or left edge.
332 var primary
= this.primaryDisplay_
;
333 var secondary
= this.secondaryDisplay_
;
335 if (this.layout_
== options
.SecondaryDisplayLayout
.LEFT
||
336 this.layout_
== options
.SecondaryDisplayLayout
.RIGHT
) {
337 offset
= secondary
.div
.offsetTop
- primary
.div
.offsetTop
;
339 offset
= secondary
.div
.offsetLeft
- primary
.div
.offsetLeft
;
341 chrome
.send('setDisplayLayout',
342 [this.layout_
, offset
/ this.visualScale_
]);
346 * Snaps the region [point, width] to [basePoint, baseWidth] if
347 * the [point, width] is close enough to the base's edge.
348 * @param {number} point The starting point of the region.
349 * @param {number} width The width of the region.
350 * @param {number} basePoint The starting point of the base region.
351 * @param {number} baseWidth The width of the base region.
352 * @return {number} The moved point. Returns point itself if it doesn't
353 * need to snap to the edge.
356 snapToEdge_: function(point
, width
, basePoint
, baseWidth
) {
357 // If the edge of the regions is smaller than this, it will snap to the
359 /** @const */ var SNAP_DISTANCE_PX
= 16;
361 var startDiff
= Math
.abs(point
- basePoint
);
362 var endDiff
= Math
.abs(point
+ width
- (basePoint
+ baseWidth
));
363 // Prefer the closer one if both edges are close enough.
364 if (startDiff
< SNAP_DISTANCE_PX
&& startDiff
< endDiff
)
366 else if (endDiff
< SNAP_DISTANCE_PX
)
367 return basePoint
+ baseWidth
- width
;
373 * Processes the actual dragging of display rectangle.
374 * @param {Event} e The event which triggers this drag.
375 * @param {Object} eventLocation The location where the event happens.
378 processDragging_: function(e
, eventLocation
) {
383 for (var i
= 0; i
< this.displays_
.length
; i
++) {
384 if (this.displays_
[i
] == this.dragging_
.display
) {
394 // Note that current code of moving display-rectangles doesn't work
395 // if there are >=3 displays. This is our assumption for M21.
396 // TODO(mukai): Fix the code to allow >=3 displays.
398 x
: this.dragging_
.originalLocation
.x
+
399 (eventLocation
.x
- this.dragging_
.eventLocation
.x
),
400 y
: this.dragging_
.originalLocation
.y
+
401 (eventLocation
.y
- this.dragging_
.eventLocation
.y
)
404 var baseDiv
= this.dragging_
.display
.isPrimary
?
405 this.secondaryDisplay_
.div
: this.primaryDisplay_
.div
;
406 var draggingDiv
= this.dragging_
.display
.div
;
408 newPosition
.x
= this.snapToEdge_(newPosition
.x
, draggingDiv
.offsetWidth
,
409 baseDiv
.offsetLeft
, baseDiv
.offsetWidth
);
410 newPosition
.y
= this.snapToEdge_(newPosition
.y
, draggingDiv
.offsetHeight
,
411 baseDiv
.offsetTop
, baseDiv
.offsetHeight
);
414 x
: newPosition
.x
+ draggingDiv
.offsetWidth
/ 2,
415 y
: newPosition
.y
+ draggingDiv
.offsetHeight
/ 2
419 x
: baseDiv
.offsetLeft
,
420 y
: baseDiv
.offsetTop
,
421 width
: baseDiv
.offsetWidth
,
422 height
: baseDiv
.offsetHeight
424 switch (getPositionToRectangle(baseBounds
, newCenter
)) {
425 case options
.SecondaryDisplayLayout
.RIGHT
:
426 this.layout_
= this.dragging_
.display
.isPrimary
?
427 options
.SecondaryDisplayLayout
.LEFT
:
428 options
.SecondaryDisplayLayout
.RIGHT
;
430 case options
.SecondaryDisplayLayout
.LEFT
:
431 this.layout_
= this.dragging_
.display
.isPrimary
?
432 options
.SecondaryDisplayLayout
.RIGHT
:
433 options
.SecondaryDisplayLayout
.LEFT
;
435 case options
.SecondaryDisplayLayout
.TOP
:
436 this.layout_
= this.dragging_
.display
.isPrimary
?
437 options
.SecondaryDisplayLayout
.BOTTOM
:
438 options
.SecondaryDisplayLayout
.TOP
;
440 case options
.SecondaryDisplayLayout
.BOTTOM
:
441 this.layout_
= this.dragging_
.display
.isPrimary
?
442 options
.SecondaryDisplayLayout
.TOP
:
443 options
.SecondaryDisplayLayout
.BOTTOM
;
447 if (this.layout_
== options
.SecondaryDisplayLayout
.LEFT
||
448 this.layout_
== options
.SecondaryDisplayLayout
.RIGHT
) {
449 if (newPosition
.y
> baseDiv
.offsetTop
+ baseDiv
.offsetHeight
)
450 this.layout_
= this.dragging_
.display
.isPrimary
?
451 options
.SecondaryDisplayLayout
.TOP
:
452 options
.SecondaryDisplayLayout
.BOTTOM
;
453 else if (newPosition
.y
+ draggingDiv
.offsetHeight
<
455 this.layout_
= this.dragging_
.display
.isPrimary
?
456 options
.SecondaryDisplayLayout
.BOTTOM
:
457 options
.SecondaryDisplayLayout
.TOP
;
459 if (newPosition
.x
> baseDiv
.offsetLeft
+ baseDiv
.offsetWidth
)
460 this.layout_
= this.dragging_
.display
.isPrimary
?
461 options
.SecondaryDisplayLayout
.LEFT
:
462 options
.SecondaryDisplayLayout
.RIGHT
;
463 else if (newPosition
.x
+ draggingDiv
.offsetWidth
<
465 this.layout_
= this.dragging_
.display
.isPrimary
?
466 options
.SecondaryDisplayLayout
.RIGHT
:
467 options
.SecondaryDisplayLayout
.LEFT
;
471 if (!this.dragging_
.display
.isPrimary
) {
472 layoutToBase
= this.layout_
;
474 switch (this.layout_
) {
475 case options
.SecondaryDisplayLayout
.RIGHT
:
476 layoutToBase
= options
.SecondaryDisplayLayout
.LEFT
;
478 case options
.SecondaryDisplayLayout
.LEFT
:
479 layoutToBase
= options
.SecondaryDisplayLayout
.RIGHT
;
481 case options
.SecondaryDisplayLayout
.TOP
:
482 layoutToBase
= options
.SecondaryDisplayLayout
.BOTTOM
;
484 case options
.SecondaryDisplayLayout
.BOTTOM
:
485 layoutToBase
= options
.SecondaryDisplayLayout
.TOP
;
490 switch (layoutToBase
) {
491 case options
.SecondaryDisplayLayout
.RIGHT
:
492 draggingDiv
.style
.left
=
493 baseDiv
.offsetLeft
+ baseDiv
.offsetWidth
+ 'px';
494 draggingDiv
.style
.top
= newPosition
.y
+ 'px';
496 case options
.SecondaryDisplayLayout
.LEFT
:
497 draggingDiv
.style
.left
=
498 baseDiv
.offsetLeft
- draggingDiv
.offsetWidth
+ 'px';
499 draggingDiv
.style
.top
= newPosition
.y
+ 'px';
501 case options
.SecondaryDisplayLayout
.TOP
:
502 draggingDiv
.style
.top
=
503 baseDiv
.offsetTop
- draggingDiv
.offsetHeight
+ 'px';
504 draggingDiv
.style
.left
= newPosition
.x
+ 'px';
506 case options
.SecondaryDisplayLayout
.BOTTOM
:
507 draggingDiv
.style
.top
=
508 baseDiv
.offsetTop
+ baseDiv
.offsetHeight
+ 'px';
509 draggingDiv
.style
.left
= newPosition
.x
+ 'px';
517 * start dragging of a display rectangle.
518 * @param {HTMLElement} target The event target.
519 * @param {Object} eventLocation The object to hold the location where
520 * this event happens.
523 startDragging_: function(target
, eventLocation
) {
524 this.focusedIndex_
= null;
525 for (var i
= 0; i
< this.displays_
.length
; i
++) {
526 var display
= this.displays_
[i
];
527 if (display
.div
== target
||
528 (target
.offsetParent
&& target
.offsetParent
== display
.div
)) {
529 this.focusedIndex_
= i
;
534 for (var i
= 0; i
< this.displays_
.length
; i
++) {
535 var display
= this.displays_
[i
];
536 display
.div
.className
= 'displays-display';
537 if (i
!= this.focusedIndex_
)
540 display
.div
.classList
.add('displays-focused');
541 if (this.displays_
.length
> 1) {
545 x
: display
.div
.offsetLeft
, y
: display
.div
.offsetTop
547 eventLocation
: eventLocation
552 this.updateSelectedDisplayDescription_();
557 * finish the current dragging of displays.
558 * @param {Event} e The event which triggers this.
561 endDragging_: function(e
) {
562 this.lastTouchLocation_
= null;
563 if (this.dragging_
) {
564 // Make sure the dragging location is connected.
565 var baseDiv
= this.dragging_
.display
.isPrimary
?
566 this.secondaryDisplay_
.div
: this.primaryDisplay_
.div
;
567 var draggingDiv
= this.dragging_
.display
.div
;
568 if (this.layout_
== options
.SecondaryDisplayLayout
.LEFT
||
569 this.layout_
== options
.SecondaryDisplayLayout
.RIGHT
) {
570 var top
= Math
.max(draggingDiv
.offsetTop
,
571 baseDiv
.offsetTop
- draggingDiv
.offsetHeight
+
574 baseDiv
.offsetTop
+ baseDiv
.offsetHeight
-
576 draggingDiv
.style
.top
= top
+ 'px';
578 var left
= Math
.max(draggingDiv
.offsetLeft
,
579 baseDiv
.offsetLeft
- draggingDiv
.offsetWidth
+
581 left
= Math
.min(left
,
582 baseDiv
.offsetLeft
+ baseDiv
.offsetWidth
-
584 draggingDiv
.style
.left
= left
+ 'px';
586 var originalPosition
= this.dragging_
.display
.originalPosition
;
587 if (originalPosition
.x
!= draggingDiv
.offsetLeft
||
588 originalPosition
.y
!= draggingDiv
.offsetTop
)
590 this.dragging_
= null;
592 this.updateSelectedDisplayDescription_();
597 * Updates the description of selected display section for mirroring mode.
600 updateSelectedDisplaySectionMirroring_: function() {
601 $('display-configuration-arrow').hidden
= true;
602 $('display-options-set-primary').disabled
= true;
603 $('display-options-toggle-mirroring').disabled
= false;
604 $('selected-display-start-calibrating-overscan').disabled
= true;
605 $('display-options-orientation-selection').disabled
= true;
606 var display
= this.displays_
[0];
607 $('selected-display-name').textContent
=
608 loadTimeData
.getString('mirroringDisplay');
609 var resolution
= $('display-options-resolution-selection');
610 var option
= document
.createElement('option');
611 option
.value
= 'default';
612 option
.textContent
= display
.width
+ 'x' + display
.height
;
613 resolution
.appendChild(option
);
614 resolution
.disabled
= true;
618 * Updates the description of selected display section when no display is
622 updateSelectedDisplaySectionNoSelected_: function() {
623 $('display-configuration-arrow').hidden
= true;
624 $('display-options-set-primary').disabled
= true;
625 $('display-options-toggle-mirroring').disabled
= true;
626 $('selected-display-start-calibrating-overscan').disabled
= true;
627 $('display-options-orientation-selection').disabled
= true;
628 $('selected-display-name').textContent
= '';
629 var resolution
= $('display-options-resolution-selection');
630 resolution
.appendChild(document
.createElement('option'));
631 resolution
.disabled
= true;
635 * Updates the description of selected display section for the selected
637 * @param {Object} display The selected display object.
640 updateSelectedDisplaySectionForDisplay_: function(display
) {
641 var arrow
= $('display-configuration-arrow');
642 arrow
.hidden
= false;
643 // Adding 1 px to the position to fit the border line and the border in
645 arrow
.style
.top
= $('display-configurations').offsetTop
-
646 arrow
.offsetHeight
/ 2 + 'px';
647 arrow
.style
.left
= display
.div
.offsetLeft
+
648 display
.div
.offsetWidth
/ 2 - arrow
.offsetWidth
/ 2 + 'px';
650 $('display-options-set-primary').disabled
= display
.isPrimary
;
651 $('display-options-toggle-mirroring').disabled
=
652 (this.displays_
.length
<= 1);
653 $('selected-display-start-calibrating-overscan').disabled
=
656 var orientation
= $('display-options-orientation-selection');
657 orientation
.disabled
= false;
658 var orientationOptions
= orientation
.getElementsByTagName('option');
659 orientationOptions
[display
.orientation
].selected
= true;
661 $('selected-display-name').textContent
= display
.name
;
663 var resolution
= $('display-options-resolution-selection');
664 if (display
.resolutions
.length
<= 1) {
665 var option
= document
.createElement('option');
666 option
.value
= 'default';
667 option
.textContent
= display
.width
+ 'x' + display
.height
;
668 option
.selected
= true;
669 resolution
.appendChild(option
);
670 resolution
.disabled
= true;
673 for (var i
= 0; i
< display
.resolutions
.length
; i
++) {
674 var option
= document
.createElement('option');
676 option
.textContent
= display
.resolutions
[i
].width
+ 'x' +
677 display
.resolutions
[i
].height
;
678 if (display
.resolutions
[i
].isBest
) {
679 option
.textContent
+= ' ' +
680 loadTimeData
.getString('annotateBest');
681 } else if (display
.resolutions
[i
].isNative
) {
682 option
.textContent
+= ' ' +
683 loadTimeData
.getString('annotateNative');
685 if (display
.resolutions
[i
].deviceScaleFactor
&& previousOption
&&
686 previousOption
.textContent
== option
.textContent
) {
687 option
.textContent
+=
688 ' (' + display
.resolutions
[i
].deviceScaleFactor
+ 'x)';
690 option
.selected
= display
.resolutions
[i
].selected
;
691 resolution
.appendChild(option
);
692 previousOption
= option
;
694 resolution
.disabled
= (display
.resolutions
.length
<= 1);
697 if (display
.availableColorProfiles
.length
<= 1) {
698 $('selected-display-color-profile-row').hidden
= true;
700 $('selected-display-color-profile-row').hidden
= false;
701 var profiles
= $('display-options-color-profile-selection');
702 profiles
.innerHTML
= '';
703 for (var i
= 0; i
< display
.availableColorProfiles
.length
; i
++) {
704 var option
= document
.createElement('option');
705 var colorProfile
= display
.availableColorProfiles
[i
];
706 option
.value
= colorProfile
.profileId
;
707 option
.textContent
= colorProfile
.name
;
709 display
.colorProfile
== colorProfile
.profileId
);
710 profiles
.appendChild(option
);
716 * Updates the description of the selected display section.
719 updateSelectedDisplayDescription_: function() {
720 var resolution
= $('display-options-resolution-selection');
721 resolution
.textContent
= '';
722 var orientation
= $('display-options-orientation-selection');
723 var orientationOptions
= orientation
.getElementsByTagName('option');
724 for (var i
= 0; i
< orientationOptions
.length
; i
++)
725 orientationOptions
.selected
= false;
727 if (this.mirroring_
) {
728 this.updateSelectedDisplaySectionMirroring_();
729 } else if (this.focusedIndex_
== null ||
730 this.displays_
[this.focusedIndex_
] == null) {
731 this.updateSelectedDisplaySectionNoSelected_();
733 this.updateSelectedDisplaySectionForDisplay_(
734 this.displays_
[this.focusedIndex_
]);
739 * Clears the drawing area for display rectangles.
742 resetDisplaysView_: function() {
743 var displaysViewHost
= $('display-options-displays-view-host');
744 displaysViewHost
.removeChild(displaysViewHost
.firstChild
);
745 this.displaysView_
= document
.createElement('div');
746 this.displaysView_
.id
= 'display-options-displays-view';
747 displaysViewHost
.appendChild(this.displaysView_
);
751 * Lays out the display rectangles for mirroring.
754 layoutMirroringDisplays_: function() {
755 // Offset pixels for secondary display rectangles. The offset includes the
757 /** @const */ var MIRRORING_OFFSET_PIXELS
= 3;
758 // Always show two displays because there must be two displays when
759 // the display_options is enabled. Don't rely on displays_.length because
760 // there is only one display from chrome's perspective in mirror mode.
761 /** @const */ var MIN_NUM_DISPLAYS
= 2;
762 /** @const */ var MIRRORING_VERTICAL_MARGIN
= 20;
764 // The width/height should be same as the first display:
765 var width
= Math
.ceil(this.displays_
[0].width
* this.visualScale_
);
766 var height
= Math
.ceil(this.displays_
[0].height
* this.visualScale_
);
768 var numDisplays
= Math
.max(MIN_NUM_DISPLAYS
, this.displays_
.length
);
770 var totalWidth
= width
+ numDisplays
* MIRRORING_OFFSET_PIXELS
;
771 var totalHeight
= height
+ numDisplays
* MIRRORING_OFFSET_PIXELS
;
773 this.displaysView_
.style
.height
= totalHeight
+ 'px';
775 // The displays should be centered.
777 $('display-options-displays-view').offsetWidth
/ 2 - totalWidth
/ 2;
779 for (var i
= 0; i
< numDisplays
; i
++) {
780 var div
= document
.createElement('div');
781 div
.className
= 'displays-display';
782 div
.style
.top
= i
* MIRRORING_OFFSET_PIXELS
+ 'px';
783 div
.style
.left
= i
* MIRRORING_OFFSET_PIXELS
+ offsetX
+ 'px';
784 div
.style
.width
= width
+ 'px';
785 div
.style
.height
= height
+ 'px';
786 div
.style
.zIndex
= i
;
787 // set 'display-mirrored' class for the background display rectangles.
788 if (i
!= numDisplays
- 1)
789 div
.classList
.add('display-mirrored');
790 this.displaysView_
.appendChild(div
);
795 * Creates a div element representing the specified display.
796 * @param {Object} display The display object.
797 * @param {boolean} focused True if it's focused.
800 createDisplayRectangle_: function(display
, focused
) {
801 var div
= document
.createElement('div');
803 div
.className
= 'displays-display';
805 div
.classList
.add('displays-focused');
807 // div needs to be added to the DOM tree first, otherwise offsetHeight for
808 // nameContainer below cannot be computed.
809 this.displaysView_
.appendChild(div
);
811 var nameContainer
= document
.createElement('div');
812 nameContainer
.textContent
= display
.name
;
813 div
.appendChild(nameContainer
);
814 div
.style
.width
= Math
.floor(display
.width
* this.visualScale_
) + 'px';
815 var newHeight
= Math
.floor(display
.height
* this.visualScale_
);
816 div
.style
.height
= newHeight
+ 'px';
817 nameContainer
.style
.marginTop
=
818 (newHeight
- nameContainer
.offsetHeight
) / 2 + 'px';
820 div
.onmousedown
= this.onMouseDown_
.bind(this);
821 div
.ontouchstart
= this.onTouchStart_
.bind(this);
826 * Layouts the display rectangles according to the current layout_.
829 layoutDisplays_: function() {
832 var boundingBox
= {left
: 0, right
: 0, top
: 0, bottom
: 0};
833 this.primaryDisplay_
= null;
834 this.secondaryDisplay_
= null;
835 var focusedDisplay
= null;
836 for (var i
= 0; i
< this.displays_
.length
; i
++) {
837 var display
= this.displays_
[i
];
838 if (display
.isPrimary
)
839 this.primaryDisplay_
= display
;
841 this.secondaryDisplay_
= display
;
842 if (i
== this.focusedIndex_
)
843 focusedDisplay
= display
;
845 boundingBox
.left
= Math
.min(boundingBox
.left
, display
.x
);
846 boundingBox
.right
= Math
.max(
847 boundingBox
.right
, display
.x
+ display
.width
);
848 boundingBox
.top
= Math
.min(boundingBox
.top
, display
.y
);
849 boundingBox
.bottom
= Math
.max(
850 boundingBox
.bottom
, display
.y
+ display
.height
);
851 maxWidth
= Math
.max(maxWidth
, display
.width
);
852 maxHeight
= Math
.max(maxHeight
, display
.height
);
854 if (!this.primaryDisplay_
)
857 // Make the margin around the bounding box.
858 var areaWidth
= boundingBox
.right
- boundingBox
.left
+ maxWidth
;
859 var areaHeight
= boundingBox
.bottom
- boundingBox
.top
+ maxHeight
;
861 // Calculates the scale by the width since horizontal size is more strict.
862 // TODO(mukai): Adds the check of vertical size in case.
863 this.visualScale_
= Math
.min(
864 VISUAL_SCALE
, this.displaysView_
.offsetWidth
/ areaWidth
);
866 // Prepare enough area for thisplays_view by adding the maximum height.
867 this.displaysView_
.style
.height
=
868 Math
.ceil(areaHeight
* this.visualScale_
) + 'px';
870 // Centering the bounding box of the display rectangles.
872 x
: Math
.floor(this.displaysView_
.offsetWidth
/ 2 -
873 (boundingBox
.right
+ boundingBox
.left
) * this.visualScale_
/ 2),
874 y
: Math
.floor(this.displaysView_
.offsetHeight
/ 2 -
875 (boundingBox
.bottom
+ boundingBox
.top
) * this.visualScale_
/ 2)
878 // Layouting the display rectangles. First layout the primaryDisplay and
879 // then layout the secondary which is attaching to the primary.
880 var primaryDiv
= this.createDisplayRectangle_(
881 this.primaryDisplay_
, this.primaryDisplay_
== focusedDisplay
);
882 primaryDiv
.style
.left
=
883 Math
.floor(this.primaryDisplay_
.x
* this.visualScale_
) +
885 primaryDiv
.style
.top
=
886 Math
.floor(this.primaryDisplay_
.y
* this.visualScale_
) +
888 this.primaryDisplay_
.originalPosition
= {
889 x
: primaryDiv
.offsetLeft
, y
: primaryDiv
.offsetTop
};
891 if (this.secondaryDisplay_
) {
892 var secondaryDiv
= this.createDisplayRectangle_(
893 this.secondaryDisplay_
, this.secondaryDisplay_
== focusedDisplay
);
894 // Don't trust the secondary display's x or y, because it may cause a
895 // 1px gap due to rounding, which will create a fake update on end
896 // dragging. See crbug.com/386401
897 switch (this.layout_
) {
898 case options
.SecondaryDisplayLayout
.TOP
:
899 secondaryDiv
.style
.left
=
900 Math
.floor(this.secondaryDisplay_
.x
* this.visualScale_
) +
902 secondaryDiv
.style
.top
=
903 primaryDiv
.offsetTop
- secondaryDiv
.offsetHeight
+ 'px';
905 case options
.SecondaryDisplayLayout
.RIGHT
:
906 secondaryDiv
.style
.left
=
907 primaryDiv
.offsetLeft
+ primaryDiv
.offsetWidth
+ 'px';
908 secondaryDiv
.style
.top
=
909 Math
.floor(this.secondaryDisplay_
.y
* this.visualScale_
) +
912 case options
.SecondaryDisplayLayout
.BOTTOM
:
913 secondaryDiv
.style
.left
=
914 Math
.floor(this.secondaryDisplay_
.x
* this.visualScale_
) +
916 secondaryDiv
.style
.top
=
917 primaryDiv
.offsetTop
+ primaryDiv
.offsetHeight
+ 'px';
919 case options
.SecondaryDisplayLayout
.LEFT
:
920 secondaryDiv
.style
.left
=
921 primaryDiv
.offsetLeft
- secondaryDiv
.offsetWidth
+ 'px';
922 secondaryDiv
.style
.top
=
923 Math
.floor(this.secondaryDisplay_
.y
* this.visualScale_
) +
927 this.secondaryDisplay_
.originalPosition
= {
928 x
: secondaryDiv
.offsetLeft
, y
: secondaryDiv
.offsetTop
};
933 * Called when the display arrangement has changed.
934 * @param {boolean} mirroring Whether current mode is mirroring or not.
935 * @param {Array<options.DisplayInfo>} displays The list of the display
937 * @param {options.SecondaryDisplayLayout} layout The layout strategy.
938 * @param {number} offset The offset of the secondary display.
941 onDisplayChanged_: function(mirroring
, displays
, layout
, offset
) {
945 var hasExternal
= false;
946 for (var i
= 0; i
< displays
.length
; i
++) {
947 if (!displays
[i
].isInternal
) {
953 this.layout_
= layout
;
955 $('display-options-toggle-mirroring').textContent
=
956 loadTimeData
.getString(
957 mirroring
? 'stopMirroring' : 'startMirroring');
959 // Focus to the first display next to the primary one when |displays| list
962 this.focusedIndex_
= null;
963 } else if (this.mirroring_
!= mirroring
||
964 this.displays_
.length
!= displays
.length
) {
965 this.focusedIndex_
= 0;
968 this.mirroring_
= mirroring
;
969 this.displays_
= displays
;
971 this.resetDisplaysView_();
973 this.layoutMirroringDisplays_();
975 this.layoutDisplays_();
977 this.updateSelectedDisplayDescription_();
981 DisplayOptions
.setDisplayInfo = function(
982 mirroring
, displays
, layout
, offset
) {
983 DisplayOptions
.getInstance().onDisplayChanged_(
984 mirroring
, displays
, layout
, offset
);
989 DisplayOptions
: DisplayOptions