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