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 initializePage: function() {
175 Page.prototype.initializePage.call(this);
177 $('display-options-toggle-mirroring').onclick = (function() {
178 this.mirroring_ = !this.mirroring_;
179 chrome.send('setMirroring', [this.mirroring_]);
182 var container = $('display-options-displays-view-host');
183 container.onmousemove = this.onMouseMove_.bind(this);
184 window.addEventListener('mouseup', this.endDragging_.bind(this), true);
185 container.ontouchmove = this.onTouchMove_.bind(this);
186 container.ontouchend = this.endDragging_.bind(this);
188 $('display-options-set-primary').onclick = (function() {
189 chrome.send('setPrimary', [this.displays_[this.focusedIndex_].id]);
191 $('display-options-resolution-selection').onchange = (function(ev) {
192 var display = this.displays_[this.focusedIndex_];
193 var resolution = display.resolutions[ev.target.value];
194 chrome.send('setDisplayMode', [display.id, resolution]);
196 $('display-options-orientation-selection').onchange = (function(ev) {
197 chrome.send('setOrientation', [this.displays_[this.focusedIndex_].id,
200 $('display-options-color-profile-selection').onchange = (function(ev) {
201 chrome.send('setColorProfile', [this.displays_[this.focusedIndex_].id,
204 $('selected-display-start-calibrating-overscan').onclick = (function() {
205 // Passes the target display ID. Do not specify it through URL hash,
206 // we do not care back/forward.
207 var displayOverscan = options.DisplayOverscan.getInstance();
208 displayOverscan.setDisplayId(this.displays_[this.focusedIndex_].id);
209 PageManager.showPageByName('displayOverscan');
210 chrome.send('coreOptionsUserMetricsAction',
211 ['Options_DisplaySetOverscan']);
216 didShowPage: function() {
217 var optionTitles = document.getElementsByClassName(
218 'selected-display-option-title');
220 for (var i = 0; i < optionTitles.length; i++)
221 maxSize = Math.max(maxSize, optionTitles[i].clientWidth);
222 for (var i = 0; i < optionTitles.length; i++)
223 optionTitles[i].style.width = maxSize + 'px';
224 chrome.send('getDisplayInfo');
228 * Mouse move handler for dragging display rectangle.
229 * @param {Event} e The mouse move event.
232 onMouseMove_: function(e) {
233 return this.processDragging_(e, {x: e.pageX, y: e.pageY});
237 * Touch move handler for dragging display rectangle.
238 * @param {Event} e The touch move event.
241 onTouchMove_: function(e) {
242 if (e.touches.length != 1)
245 var touchLocation = {x: e.touches[0].pageX, y: e.touches[0].pageY};
246 // Touch move events happen even if the touch location doesn't change, but
247 // it doesn't need to process the dragging. Since sometimes the touch
248 // position changes slightly even though the user doesn't think to move
249 // the finger, very small move is just ignored.
250 /** @const */ var IGNORABLE_TOUCH_MOVE_PX = 1;
251 var xDiff = Math.abs(touchLocation.x - this.lastTouchLocation_.x);
252 var yDiff = Math.abs(touchLocation.y - this.lastTouchLocation_.y);
253 if (xDiff <= IGNORABLE_TOUCH_MOVE_PX &&
254 yDiff <= IGNORABLE_TOUCH_MOVE_PX) {
258 this.lastTouchLocation_ = touchLocation;
259 return this.processDragging_(e, touchLocation);
263 * Mouse down handler for dragging display rectangle.
264 * @param {Event} e The mouse down event.
267 onMouseDown_: function(e) {
275 var target = assertInstanceof(e.target, HTMLElement);
276 return this.startDragging_(target, {x: e.pageX, y: e.pageY});
280 * Touch start handler for dragging display rectangle.
281 * @param {Event} e The touch start event.
284 onTouchStart_: function(e) {
288 if (e.touches.length != 1)
292 var touch = e.touches[0];
293 this.lastTouchLocation_ = {x: touch.pageX, y: touch.pageY};
294 var target = assertInstanceof(e.target, HTMLElement);
295 return this.startDragging_(target, this.lastTouchLocation_);
299 * Collects the current data and sends it to Chrome.
302 applyResult_: function() {
303 // Offset is calculated from top or left edge.
304 var primary = this.primaryDisplay_;
305 var secondary = this.secondaryDisplay_;
307 if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
308 this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
309 offset = secondary.div.offsetTop - primary.div.offsetTop;
311 offset = secondary.div.offsetLeft - primary.div.offsetLeft;
313 chrome.send('setDisplayLayout',
314 [this.layout_, offset / this.visualScale_]);
318 * Snaps the region [point, width] to [basePoint, baseWidth] if
319 * the [point, width] is close enough to the base's edge.
320 * @param {number} point The starting point of the region.
321 * @param {number} width The width of the region.
322 * @param {number} basePoint The starting point of the base region.
323 * @param {number} baseWidth The width of the base region.
324 * @return {number} The moved point. Returns point itself if it doesn't
325 * need to snap to the edge.
328 snapToEdge_: function(point, width, basePoint, baseWidth) {
329 // If the edge of the regions is smaller than this, it will snap to the
331 /** @const */ var SNAP_DISTANCE_PX = 16;
333 var startDiff = Math.abs(point - basePoint);
334 var endDiff = Math.abs(point + width - (basePoint + baseWidth));
335 // Prefer the closer one if both edges are close enough.
336 if (startDiff < SNAP_DISTANCE_PX && startDiff < endDiff)
338 else if (endDiff < SNAP_DISTANCE_PX)
339 return basePoint + baseWidth - width;
345 * Processes the actual dragging of display rectangle.
346 * @param {Event} e The event which triggers this drag.
347 * @param {Object} eventLocation The location where the event happens.
350 processDragging_: function(e, eventLocation) {
355 for (var i = 0; i < this.displays_.length; i++) {
356 if (this.displays_[i] == this.dragging_.display) {
366 // Note that current code of moving display-rectangles doesn't work
367 // if there are >=3 displays. This is our assumption for M21.
368 // TODO(mukai): Fix the code to allow >=3 displays.
370 x: this.dragging_.originalLocation.x +
371 (eventLocation.x - this.dragging_.eventLocation.x),
372 y: this.dragging_.originalLocation.y +
373 (eventLocation.y - this.dragging_.eventLocation.y)
376 var baseDiv = this.dragging_.display.isPrimary ?
377 this.secondaryDisplay_.div : this.primaryDisplay_.div;
378 var draggingDiv = this.dragging_.display.div;
380 newPosition.x = this.snapToEdge_(newPosition.x, draggingDiv.offsetWidth,
381 baseDiv.offsetLeft, baseDiv.offsetWidth);
382 newPosition.y = this.snapToEdge_(newPosition.y, draggingDiv.offsetHeight,
383 baseDiv.offsetTop, baseDiv.offsetHeight);
386 x: newPosition.x + draggingDiv.offsetWidth / 2,
387 y: newPosition.y + draggingDiv.offsetHeight / 2
391 x: baseDiv.offsetLeft,
392 y: baseDiv.offsetTop,
393 width: baseDiv.offsetWidth,
394 height: baseDiv.offsetHeight
396 switch (getPositionToRectangle(baseBounds, newCenter)) {
397 case options.SecondaryDisplayLayout.RIGHT:
398 this.layout_ = this.dragging_.display.isPrimary ?
399 options.SecondaryDisplayLayout.LEFT :
400 options.SecondaryDisplayLayout.RIGHT;
402 case options.SecondaryDisplayLayout.LEFT:
403 this.layout_ = this.dragging_.display.isPrimary ?
404 options.SecondaryDisplayLayout.RIGHT :
405 options.SecondaryDisplayLayout.LEFT;
407 case options.SecondaryDisplayLayout.TOP:
408 this.layout_ = this.dragging_.display.isPrimary ?
409 options.SecondaryDisplayLayout.BOTTOM :
410 options.SecondaryDisplayLayout.TOP;
412 case options.SecondaryDisplayLayout.BOTTOM:
413 this.layout_ = this.dragging_.display.isPrimary ?
414 options.SecondaryDisplayLayout.TOP :
415 options.SecondaryDisplayLayout.BOTTOM;
419 if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
420 this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
421 if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight)
422 this.layout_ = this.dragging_.display.isPrimary ?
423 options.SecondaryDisplayLayout.TOP :
424 options.SecondaryDisplayLayout.BOTTOM;
425 else if (newPosition.y + draggingDiv.offsetHeight <
427 this.layout_ = this.dragging_.display.isPrimary ?
428 options.SecondaryDisplayLayout.BOTTOM :
429 options.SecondaryDisplayLayout.TOP;
431 if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth)
432 this.layout_ = this.dragging_.display.isPrimary ?
433 options.SecondaryDisplayLayout.LEFT :
434 options.SecondaryDisplayLayout.RIGHT;
435 else if (newPosition.x + draggingDiv.offsetWidth <
437 this.layout_ = this.dragging_.display.isPrimary ?
438 options.SecondaryDisplayLayout.RIGHT :
439 options.SecondaryDisplayLayout.LEFT;
443 if (!this.dragging_.display.isPrimary) {
444 layoutToBase = this.layout_;
446 switch (this.layout_) {
447 case options.SecondaryDisplayLayout.RIGHT:
448 layoutToBase = options.SecondaryDisplayLayout.LEFT;
450 case options.SecondaryDisplayLayout.LEFT:
451 layoutToBase = options.SecondaryDisplayLayout.RIGHT;
453 case options.SecondaryDisplayLayout.TOP:
454 layoutToBase = options.SecondaryDisplayLayout.BOTTOM;
456 case options.SecondaryDisplayLayout.BOTTOM:
457 layoutToBase = options.SecondaryDisplayLayout.TOP;
462 switch (layoutToBase) {
463 case options.SecondaryDisplayLayout.RIGHT:
464 draggingDiv.style.left =
465 baseDiv.offsetLeft + baseDiv.offsetWidth + 'px';
466 draggingDiv.style.top = newPosition.y + 'px';
468 case options.SecondaryDisplayLayout.LEFT:
469 draggingDiv.style.left =
470 baseDiv.offsetLeft - draggingDiv.offsetWidth + 'px';
471 draggingDiv.style.top = newPosition.y + 'px';
473 case options.SecondaryDisplayLayout.TOP:
474 draggingDiv.style.top =
475 baseDiv.offsetTop - draggingDiv.offsetHeight + 'px';
476 draggingDiv.style.left = newPosition.x + 'px';
478 case options.SecondaryDisplayLayout.BOTTOM:
479 draggingDiv.style.top =
480 baseDiv.offsetTop + baseDiv.offsetHeight + 'px';
481 draggingDiv.style.left = newPosition.x + 'px';
489 * start dragging of a display rectangle.
490 * @param {HTMLElement} target The event target.
491 * @param {Object} eventLocation The object to hold the location where
492 * this event happens.
495 startDragging_: function(target, eventLocation) {
496 this.focusedIndex_ = null;
497 for (var i = 0; i < this.displays_.length; i++) {
498 var display = this.displays_[i];
499 if (display.div == target ||
500 (target.offsetParent && target.offsetParent == display.div)) {
501 this.focusedIndex_ = i;
506 for (var i = 0; i < this.displays_.length; i++) {
507 var display = this.displays_[i];
508 display.div.className = 'displays-display';
509 if (i != this.focusedIndex_)
512 display.div.classList.add('displays-focused');
513 if (this.displays_.length > 1) {
517 x: display.div.offsetLeft, y: display.div.offsetTop
519 eventLocation: eventLocation
524 this.updateSelectedDisplayDescription_();
529 * finish the current dragging of displays.
530 * @param {Event} e The event which triggers this.
533 endDragging_: function(e) {
534 this.lastTouchLocation_ = null;
535 if (this.dragging_) {
536 // Make sure the dragging location is connected.
537 var baseDiv = this.dragging_.display.isPrimary ?
538 this.secondaryDisplay_.div : this.primaryDisplay_.div;
539 var draggingDiv = this.dragging_.display.div;
540 if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
541 this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
542 var top = Math.max(draggingDiv.offsetTop,
543 baseDiv.offsetTop - draggingDiv.offsetHeight +
546 baseDiv.offsetTop + baseDiv.offsetHeight -
548 draggingDiv.style.top = top + 'px';
550 var left = Math.max(draggingDiv.offsetLeft,
551 baseDiv.offsetLeft - draggingDiv.offsetWidth +
553 left = Math.min(left,
554 baseDiv.offsetLeft + baseDiv.offsetWidth -
556 draggingDiv.style.left = left + 'px';
558 var originalPosition = this.dragging_.display.originalPosition;
559 if (originalPosition.x != draggingDiv.offsetLeft ||
560 originalPosition.y != draggingDiv.offsetTop)
562 this.dragging_ = null;
564 this.updateSelectedDisplayDescription_();
569 * Updates the description of selected display section for mirroring mode.
572 updateSelectedDisplaySectionMirroring_: function() {
573 $('display-configuration-arrow').hidden = true;
574 $('display-options-set-primary').disabled = true;
575 $('display-options-toggle-mirroring').disabled = false;
576 $('selected-display-start-calibrating-overscan').disabled = true;
577 $('display-options-orientation-selection').disabled = true;
578 var display = this.displays_[0];
579 $('selected-display-name').textContent =
580 loadTimeData.getString('mirroringDisplay');
581 var resolution = $('display-options-resolution-selection');
582 var option = document.createElement('option');
583 option.value = 'default';
584 option.textContent = display.width + 'x' + display.height;
585 resolution.appendChild(option);
586 resolution.disabled = true;
590 * Updates the description of selected display section when no display is
594 updateSelectedDisplaySectionNoSelected_: function() {
595 $('display-configuration-arrow').hidden = true;
596 $('display-options-set-primary').disabled = true;
597 $('display-options-toggle-mirroring').disabled = true;
598 $('selected-display-start-calibrating-overscan').disabled = true;
599 $('display-options-orientation-selection').disabled = true;
600 $('selected-display-name').textContent = '';
601 var resolution = $('display-options-resolution-selection');
602 resolution.appendChild(document.createElement('option'));
603 resolution.disabled = true;
607 * Updates the description of selected display section for the selected
609 * @param {Object} display The selected display object.
612 updateSelectedDisplaySectionForDisplay_: function(display) {
613 var arrow = $('display-configuration-arrow');
614 arrow.hidden = false;
615 // Adding 1 px to the position to fit the border line and the border in
617 arrow.style.top = $('display-configurations').offsetTop -
618 arrow.offsetHeight / 2 + 'px';
619 arrow.style.left = display.div.offsetLeft +
620 display.div.offsetWidth / 2 - arrow.offsetWidth / 2 + 'px';
622 $('display-options-set-primary').disabled = display.isPrimary;
623 $('display-options-toggle-mirroring').disabled =
624 (this.displays_.length <= 1);
625 $('selected-display-start-calibrating-overscan').disabled =
628 var orientation = $('display-options-orientation-selection');
629 orientation.disabled = false;
630 var orientationOptions = orientation.getElementsByTagName('option');
631 orientationOptions[display.orientation].selected = true;
633 $('selected-display-name').textContent = display.name;
635 var resolution = $('display-options-resolution-selection');
636 if (display.resolutions.length <= 1) {
637 var option = document.createElement('option');
638 option.value = 'default';
639 option.textContent = display.width + 'x' + display.height;
640 option.selected = true;
641 resolution.appendChild(option);
642 resolution.disabled = true;
645 for (var i = 0; i < display.resolutions.length; i++) {
646 var option = document.createElement('option');
648 option.textContent = display.resolutions[i].width + 'x' +
649 display.resolutions[i].height;
650 if (display.resolutions[i].isBest) {
651 option.textContent += ' ' +
652 loadTimeData.getString('annotateBest');
653 } else if (display.resolutions[i].isNative) {
654 option.textContent += ' ' +
655 loadTimeData.getString('annotateNative');
657 if (display.resolutions[i].deviceScaleFactor && previousOption &&
658 previousOption.textContent == option.textContent) {
659 option.textContent +=
660 ' (' + display.resolutions[i].deviceScaleFactor + 'x)';
662 option.selected = display.resolutions[i].selected;
663 resolution.appendChild(option);
664 previousOption = option;
666 resolution.disabled = (display.resolutions.length <= 1);
669 if (display.availableColorProfiles.length <= 1) {
670 $('selected-display-color-profile-row').hidden = true;
672 $('selected-display-color-profile-row').hidden = false;
673 var profiles = $('display-options-color-profile-selection');
674 profiles.innerHTML = '';
675 for (var i = 0; i < display.availableColorProfiles.length; i++) {
676 var option = document.createElement('option');
677 var colorProfile = display.availableColorProfiles[i];
678 option.value = colorProfile.profileId;
679 option.textContent = colorProfile.name;
681 display.colorProfile == colorProfile.profileId);
682 profiles.appendChild(option);
688 * Updates the description of the selected display section.
691 updateSelectedDisplayDescription_: function() {
692 var resolution = $('display-options-resolution-selection');
693 resolution.textContent = '';
694 var orientation = $('display-options-orientation-selection');
695 var orientationOptions = orientation.getElementsByTagName('option');
696 for (var i = 0; i < orientationOptions.length; i++)
697 orientationOptions.selected = false;
699 if (this.mirroring_) {
700 this.updateSelectedDisplaySectionMirroring_();
701 } else if (this.focusedIndex_ == null ||
702 this.displays_[this.focusedIndex_] == null) {
703 this.updateSelectedDisplaySectionNoSelected_();
705 this.updateSelectedDisplaySectionForDisplay_(
706 this.displays_[this.focusedIndex_]);
711 * Clears the drawing area for display rectangles.
714 resetDisplaysView_: function() {
715 var displaysViewHost = $('display-options-displays-view-host');
716 displaysViewHost.removeChild(displaysViewHost.firstChild);
717 this.displaysView_ = document.createElement('div');
718 this.displaysView_.id = 'display-options-displays-view';
719 displaysViewHost.appendChild(this.displaysView_);
723 * Lays out the display rectangles for mirroring.
726 layoutMirroringDisplays_: function() {
727 // Offset pixels for secondary display rectangles. The offset includes the
729 /** @const */ var MIRRORING_OFFSET_PIXELS = 3;
730 // Always show two displays because there must be two displays when
731 // the display_options is enabled. Don't rely on displays_.length because
732 // there is only one display from chrome's perspective in mirror mode.
733 /** @const */ var MIN_NUM_DISPLAYS = 2;
734 /** @const */ var MIRRORING_VERTICAL_MARGIN = 20;
736 // The width/height should be same as the first display:
737 var width = Math.ceil(this.displays_[0].width * this.visualScale_);
738 var height = Math.ceil(this.displays_[0].height * this.visualScale_);
740 var numDisplays = Math.max(MIN_NUM_DISPLAYS, this.displays_.length);
742 var totalWidth = width + numDisplays * MIRRORING_OFFSET_PIXELS;
743 var totalHeight = height + numDisplays * MIRRORING_OFFSET_PIXELS;
745 this.displaysView_.style.height = totalHeight + 'px';
746 this.displaysView_.classList.add(
747 'display-options-displays-view-mirroring');
749 // The displays should be centered.
751 $('display-options-displays-view').offsetWidth / 2 - totalWidth / 2;
753 for (var i = 0; i < numDisplays; i++) {
754 var div = document.createElement('div');
755 div.className = 'displays-display';
756 div.style.top = i * MIRRORING_OFFSET_PIXELS + 'px';
757 div.style.left = i * MIRRORING_OFFSET_PIXELS + offsetX + 'px';
758 div.style.width = width + 'px';
759 div.style.height = height + 'px';
760 div.style.zIndex = i;
761 // set 'display-mirrored' class for the background display rectangles.
762 if (i != numDisplays - 1)
763 div.classList.add('display-mirrored');
764 this.displaysView_.appendChild(div);
769 * Creates a div element representing the specified display.
770 * @param {Object} display The display object.
771 * @param {boolean} focused True if it's focused.
774 createDisplayRectangle_: function(display, focused) {
775 var div = document.createElement('div');
777 div.className = 'displays-display';
779 div.classList.add('displays-focused');
781 // div needs to be added to the DOM tree first, otherwise offsetHeight for
782 // nameContainer below cannot be computed.
783 this.displaysView_.appendChild(div);
785 var nameContainer = document.createElement('div');
786 nameContainer.textContent = display.name;
787 div.appendChild(nameContainer);
788 div.style.width = Math.floor(display.width * this.visualScale_) + 'px';
789 var newHeight = Math.floor(display.height * this.visualScale_);
790 div.style.height = newHeight + 'px';
791 nameContainer.style.marginTop =
792 (newHeight - nameContainer.offsetHeight) / 2 + 'px';
794 div.onmousedown = this.onMouseDown_.bind(this);
795 div.ontouchstart = this.onTouchStart_.bind(this);
800 * Layouts the display rectangles according to the current layout_.
803 layoutDisplays_: function() {
806 var boundingBox = {left: 0, right: 0, top: 0, bottom: 0};
807 this.primaryDisplay_ = null;
808 this.secondaryDisplay_ = null;
809 var focusedDisplay = null;
810 for (var i = 0; i < this.displays_.length; i++) {
811 var display = this.displays_[i];
812 if (display.isPrimary)
813 this.primaryDisplay_ = display;
815 this.secondaryDisplay_ = display;
816 if (i == this.focusedIndex_)
817 focusedDisplay = display;
819 boundingBox.left = Math.min(boundingBox.left, display.x);
820 boundingBox.right = Math.max(
821 boundingBox.right, display.x + display.width);
822 boundingBox.top = Math.min(boundingBox.top, display.y);
823 boundingBox.bottom = Math.max(
824 boundingBox.bottom, display.y + display.height);
825 maxWidth = Math.max(maxWidth, display.width);
826 maxHeight = Math.max(maxHeight, display.height);
828 if (!this.primaryDisplay_)
831 // Make the margin around the bounding box.
832 var areaWidth = boundingBox.right - boundingBox.left + maxWidth;
833 var areaHeight = boundingBox.bottom - boundingBox.top + maxHeight;
835 // Calculates the scale by the width since horizontal size is more strict.
836 // TODO(mukai): Adds the check of vertical size in case.
837 this.visualScale_ = Math.min(
838 VISUAL_SCALE, this.displaysView_.offsetWidth / areaWidth);
840 // Prepare enough area for thisplays_view by adding the maximum height.
841 this.displaysView_.style.height =
842 Math.ceil(areaHeight * this.visualScale_) + 'px';
844 // Centering the bounding box of the display rectangles.
846 x: Math.floor(this.displaysView_.offsetWidth / 2 -
847 (boundingBox.right + boundingBox.left) * this.visualScale_ / 2),
848 y: Math.floor(this.displaysView_.offsetHeight / 2 -
849 (boundingBox.bottom + boundingBox.top) * this.visualScale_ / 2)
852 // Layouting the display rectangles. First layout the primaryDisplay and
853 // then layout the secondary which is attaching to the primary.
854 var primaryDiv = this.createDisplayRectangle_(
855 this.primaryDisplay_, this.primaryDisplay_ == focusedDisplay);
856 primaryDiv.style.left =
857 Math.floor(this.primaryDisplay_.x * this.visualScale_) +
859 primaryDiv.style.top =
860 Math.floor(this.primaryDisplay_.y * this.visualScale_) +
862 this.primaryDisplay_.originalPosition = {
863 x: primaryDiv.offsetLeft, y: primaryDiv.offsetTop};
865 if (this.secondaryDisplay_) {
866 var secondaryDiv = this.createDisplayRectangle_(
867 this.secondaryDisplay_, this.secondaryDisplay_ == focusedDisplay);
868 // Don't trust the secondary display's x or y, because it may cause a
869 // 1px gap due to rounding, which will create a fake update on end
870 // dragging. See crbug.com/386401
871 switch (this.layout_) {
872 case options.SecondaryDisplayLayout.TOP:
873 secondaryDiv.style.left =
874 Math.floor(this.secondaryDisplay_.x * this.visualScale_) +
876 secondaryDiv.style.top =
877 primaryDiv.offsetTop - secondaryDiv.offsetHeight + 'px';
879 case options.SecondaryDisplayLayout.RIGHT:
880 secondaryDiv.style.left =
881 primaryDiv.offsetLeft + primaryDiv.offsetWidth + 'px';
882 secondaryDiv.style.top =
883 Math.floor(this.secondaryDisplay_.y * this.visualScale_) +
886 case options.SecondaryDisplayLayout.BOTTOM:
887 secondaryDiv.style.left =
888 Math.floor(this.secondaryDisplay_.x * this.visualScale_) +
890 secondaryDiv.style.top =
891 primaryDiv.offsetTop + primaryDiv.offsetHeight + 'px';
893 case options.SecondaryDisplayLayout.LEFT:
894 secondaryDiv.style.left =
895 primaryDiv.offsetLeft - secondaryDiv.offsetWidth + 'px';
896 secondaryDiv.style.top =
897 Math.floor(this.secondaryDisplay_.y * this.visualScale_) +
901 this.secondaryDisplay_.originalPosition = {
902 x: secondaryDiv.offsetLeft, y: secondaryDiv.offsetTop};
907 * Called when the display arrangement has changed.
908 * @param {boolean} mirroring Whether current mode is mirroring or not.
909 * @param {Array.<options.DisplayInfo>} displays The list of the display
911 * @param {options.SecondaryDisplayLayout} layout The layout strategy.
912 * @param {number} offset The offset of the secondary display.
915 onDisplayChanged_: function(mirroring, displays, layout, offset) {
919 var hasExternal = false;
920 for (var i = 0; i < displays.length; i++) {
921 if (!displays[i].isInternal) {
927 this.layout_ = layout;
929 $('display-options-toggle-mirroring').textContent =
930 loadTimeData.getString(
931 mirroring ? 'stopMirroring' : 'startMirroring');
933 // Focus to the first display next to the primary one when |displays| list
936 this.focusedIndex_ = null;
937 } else if (this.mirroring_ != mirroring ||
938 this.displays_.length != displays.length) {
939 this.focusedIndex_ = 0;
942 this.mirroring_ = mirroring;
943 this.displays_ = displays;
945 this.resetDisplaysView_();
947 this.layoutMirroringDisplays_();
949 this.layoutDisplays_();
951 this.updateSelectedDisplayDescription_();
955 DisplayOptions.setDisplayInfo = function(
956 mirroring, displays, layout, offset) {
957 DisplayOptions.getInstance().onDisplayChanged_(
958 mirroring, displays, layout, offset);
963 DisplayOptions: DisplayOptions