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.define('options', function() {
6 var OptionsPage = options.OptionsPage;
8 // The scale ratio of the display rectangle to its original size.
9 /** @const */ var VISUAL_SCALE = 1 / 10;
11 // The number of pixels to share the edges between displays.
12 /** @const */ var MIN_OFFSET_OVERLAP = 5;
15 * Enumeration of secondary display layout. The value has to be same as the
16 * values in ash/display/display_controller.cc.
19 var SecondaryDisplayLayout = {
27 * Calculates the bounds of |element| relative to the page.
28 * @param {HTMLElement} element The element to be known.
29 * @return {Object} The object for the bounds, with x, y, width, and height.
31 function getBoundsInPage(element) {
33 x: element.offsetLeft,
35 width: element.offsetWidth,
36 height: element.offsetHeight
38 var parent = element.offsetParent;
39 while (parent && parent != document.body) {
40 bounds.x += parent.offsetLeft;
41 bounds.y += parent.offsetTop;
42 parent = parent.offsetParent;
48 * Gets the position of |point| to |rect|, left, right, top, or bottom.
49 * @param {Object} rect The base rectangle with x, y, width, and height.
50 * @param {Object} point The point to check the position.
51 * @return {SecondaryDisplayLayout} The position of the calculated point.
53 function getPositionToRectangle(rect, point) {
54 // Separates the area into four (LEFT/RIGHT/TOP/BOTTOM) by the diagonals of
55 // the rect, and decides which area the display should reside.
56 var diagonalSlope = rect.height / rect.width;
57 var topDownIntercept = rect.y - rect.x * diagonalSlope;
58 var bottomUpIntercept = rect.y + rect.height + rect.x * diagonalSlope;
60 if (point.y > topDownIntercept + point.x * diagonalSlope) {
61 if (point.y > bottomUpIntercept - point.x * diagonalSlope)
62 return SecondaryDisplayLayout.BOTTOM;
64 return SecondaryDisplayLayout.LEFT;
66 if (point.y > bottomUpIntercept - point.x * diagonalSlope)
67 return SecondaryDisplayLayout.RIGHT;
69 return SecondaryDisplayLayout.TOP;
74 * Encapsulated handling of the 'Display' page.
77 function DisplayOptions() {
78 OptionsPage.call(this, 'display',
79 loadTimeData.getString('displayOptionsPageTabTitle'),
80 'display-options-page');
83 cr.addSingletonGetter(DisplayOptions);
85 DisplayOptions.prototype = {
86 __proto__: OptionsPage.prototype,
89 * Whether the current output status is mirroring displays or not.
95 * The current secondary display layout.
98 layout_: SecondaryDisplayLayout.RIGHT,
101 * The array of current output displays. It also contains the display
102 * rectangles currently rendered on screen.
108 * The index for the currently focused display in the options UI. null if
115 * The primary display.
118 primaryDisplay_: null,
121 * The secondary display.
124 secondaryDisplay_: null,
127 * The container div element which contains all of the display rectangles.
133 * The scale factor of the actual display size to the drawn display
137 visualScale_: VISUAL_SCALE,
140 * The location where the last touch event happened. This is used to
141 * prevent unnecessary dragging events happen. Set to null unless it's
142 * during touch events.
145 lastTouchLocation_: null,
148 * Initialize the page.
150 initializePage: function() {
151 OptionsPage.prototype.initializePage.call(this);
153 $('display-options-toggle-mirroring').onclick = function() {
154 this.mirroring_ = !this.mirroring_;
155 chrome.send('setMirroring', [this.mirroring_]);
158 var container = $('display-options-displays-view-host');
159 container.onmousemove = this.onMouseMove_.bind(this);
160 window.addEventListener('mouseup', this.endDragging_.bind(this), true);
161 container.ontouchmove = this.onTouchMove_.bind(this);
162 container.ontouchend = this.endDragging_.bind(this);
164 $('display-options-set-primary').onclick = function() {
165 chrome.send('setPrimary', [this.displays_[this.focusedIndex_].id]);
167 $('display-options-resolution-selection').onchange = function(ev) {
168 var display = this.displays_[this.focusedIndex_];
169 var resolution = display.resolutions[ev.target.value];
170 if (resolution.scale) {
171 chrome.send('setUIScale', [display.id, resolution.scale]);
173 chrome.send('setResolution',
174 [display.id, resolution.width, resolution.height]);
177 $('display-options-orientation-selection').onchange = function(ev) {
178 chrome.send('setOrientation', [this.displays_[this.focusedIndex_].id,
181 $('selected-display-start-calibrating-overscan').onclick = function() {
182 // Passes the target display ID. Do not specify it through URL hash,
183 // we do not care back/forward.
184 var displayOverscan = options.DisplayOverscan.getInstance();
185 displayOverscan.setDisplayId(this.displays_[this.focusedIndex_].id);
186 OptionsPage.navigateToPage('displayOverscan');
189 chrome.send('getDisplayInfo');
193 didShowPage: function() {
194 var optionTitles = document.getElementsByClassName(
195 'selected-display-option-title');
197 for (var i = 0; i < optionTitles.length; i++)
198 maxSize = Math.max(maxSize, optionTitles[i].clientWidth);
199 for (var i = 0; i < optionTitles.length; i++)
200 optionTitles[i].style.width = maxSize + 'px';
204 onVisibilityChanged_: function() {
205 OptionsPage.prototype.onVisibilityChanged_(this);
207 chrome.send('getDisplayInfo');
211 * Mouse move handler for dragging display rectangle.
212 * @param {Event} e The mouse move event.
215 onMouseMove_: function(e) {
216 return this.processDragging_(e, {x: e.pageX, y: e.pageY});
220 * Touch move handler for dragging display rectangle.
221 * @param {Event} e The touch move event.
224 onTouchMove_: function(e) {
225 if (e.touches.length != 1)
228 var touchLocation = {x: e.touches[0].pageX, y: e.touches[0].pageY};
229 // Touch move events happen even if the touch location doesn't change, but
230 // it doesn't need to process the dragging. Since sometimes the touch
231 // position changes slightly even though the user doesn't think to move
232 // the finger, very small move is just ignored.
233 /** @const */ var IGNORABLE_TOUCH_MOVE_PX = 1;
234 var xDiff = Math.abs(touchLocation.x - this.lastTouchLocation_.x);
235 var yDiff = Math.abs(touchLocation.y - this.lastTouchLocation_.y);
236 if (xDiff <= IGNORABLE_TOUCH_MOVE_PX &&
237 yDiff <= IGNORABLE_TOUCH_MOVE_PX) {
241 this.lastTouchLocation_ = touchLocation;
242 return this.processDragging_(e, touchLocation);
246 * Mouse down handler for dragging display rectangle.
247 * @param {Event} e The mouse down event.
250 onMouseDown_: function(e) {
258 return this.startDragging_(e.target, {x: e.pageX, y: e.pageY});
262 * Touch start handler for dragging display rectangle.
263 * @param {Event} e The touch start event.
266 onTouchStart_: function(e) {
270 if (e.touches.length != 1)
274 var touch = e.touches[0];
275 this.lastTouchLocation_ = {x: touch.pageX, y: touch.pageY};
276 return this.startDragging_(e.target, this.lastTouchLocation_);
280 * Collects the current data and sends it to Chrome.
283 applyResult_: function() {
284 // Offset is calculated from top or left edge.
285 var primary = this.primaryDisplay_;
286 var secondary = this.secondaryDisplay_;
288 if (this.layout_ == SecondaryDisplayLayout.LEFT ||
289 this.layout_ == SecondaryDisplayLayout.RIGHT) {
290 offset = secondary.div.offsetTop - primary.div.offsetTop;
292 offset = secondary.div.offsetLeft - primary.div.offsetLeft;
294 chrome.send('setDisplayLayout',
295 [this.layout_, offset / this.visualScale_]);
299 * Snaps the region [point, width] to [basePoint, baseWidth] if
300 * the [point, width] is close enough to the base's edge.
301 * @param {number} point The starting point of the region.
302 * @param {number} width The width of the region.
303 * @param {number} basePoint The starting point of the base region.
304 * @param {number} baseWidth The width of the base region.
305 * @return {number} The moved point. Returns point itself if it doesn't
306 * need to snap to the edge.
309 snapToEdge_: function(point, width, basePoint, baseWidth) {
310 // If the edge of the regions is smaller than this, it will snap to the
312 /** @const */ var SNAP_DISTANCE_PX = 16;
314 var startDiff = Math.abs(point - basePoint);
315 var endDiff = Math.abs(point + width - (basePoint + baseWidth));
316 // Prefer the closer one if both edges are close enough.
317 if (startDiff < SNAP_DISTANCE_PX && startDiff < endDiff)
319 else if (endDiff < SNAP_DISTANCE_PX)
320 return basePoint + baseWidth - width;
326 * Processes the actual dragging of display rectangle.
327 * @param {Event} e The event which triggers this drag.
328 * @param {Object} eventLocation The location where the event happens.
331 processDragging_: function(e, eventLocation) {
336 for (var i = 0; i < this.displays_.length; i++) {
337 if (this.displays_[i] == this.dragging_.display) {
347 // Note that current code of moving display-rectangles doesn't work
348 // if there are >=3 displays. This is our assumption for M21.
349 // TODO(mukai): Fix the code to allow >=3 displays.
351 x: this.dragging_.originalLocation.x +
352 (eventLocation.x - this.dragging_.eventLocation.x),
353 y: this.dragging_.originalLocation.y +
354 (eventLocation.y - this.dragging_.eventLocation.y)
357 var baseDiv = this.dragging_.display.isPrimary ?
358 this.secondaryDisplay_.div : this.primaryDisplay_.div;
359 var draggingDiv = this.dragging_.display.div;
361 newPosition.x = this.snapToEdge_(newPosition.x, draggingDiv.offsetWidth,
362 baseDiv.offsetLeft, baseDiv.offsetWidth);
363 newPosition.y = this.snapToEdge_(newPosition.y, draggingDiv.offsetHeight,
364 baseDiv.offsetTop, baseDiv.offsetHeight);
367 x: newPosition.x + draggingDiv.offsetWidth / 2,
368 y: newPosition.y + draggingDiv.offsetHeight / 2
372 x: baseDiv.offsetLeft,
373 y: baseDiv.offsetTop,
374 width: baseDiv.offsetWidth,
375 height: baseDiv.offsetHeight
377 switch (getPositionToRectangle(baseBounds, newCenter)) {
378 case SecondaryDisplayLayout.RIGHT:
379 this.layout_ = this.dragging_.display.isPrimary ?
380 SecondaryDisplayLayout.LEFT : SecondaryDisplayLayout.RIGHT;
382 case SecondaryDisplayLayout.LEFT:
383 this.layout_ = this.dragging_.display.isPrimary ?
384 SecondaryDisplayLayout.RIGHT : SecondaryDisplayLayout.LEFT;
386 case SecondaryDisplayLayout.TOP:
387 this.layout_ = this.dragging_.display.isPrimary ?
388 SecondaryDisplayLayout.BOTTOM : SecondaryDisplayLayout.TOP;
390 case SecondaryDisplayLayout.BOTTOM:
391 this.layout_ = this.dragging_.display.isPrimary ?
392 SecondaryDisplayLayout.TOP : SecondaryDisplayLayout.BOTTOM;
396 if (this.layout_ == SecondaryDisplayLayout.LEFT ||
397 this.layout_ == SecondaryDisplayLayout.RIGHT) {
398 if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight)
399 this.layout_ = this.dragging_.display.isPrimary ?
400 SecondaryDisplayLayout.TOP : SecondaryDisplayLayout.BOTTOM;
401 else if (newPosition.y + draggingDiv.offsetHeight <
403 this.layout_ = this.dragging_.display.isPrimary ?
404 SecondaryDisplayLayout.BOTTOM : SecondaryDisplayLayout.TOP;
406 if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth)
407 this.layout_ = this.dragging_.display.isPrimary ?
408 SecondaryDisplayLayout.LEFT : SecondaryDisplayLayout.RIGHT;
409 else if (newPosition.x + draggingDiv.offsetWidth <
411 this.layout_ = this.dragging_.display.isPrimary ?
412 SecondaryDisplayLayout.RIGHT : SecondaryDisplayLayout.LEFT;
416 if (!this.dragging_.display.isPrimary) {
417 layoutToBase = this.layout_;
419 switch (this.layout_) {
420 case SecondaryDisplayLayout.RIGHT:
421 layoutToBase = SecondaryDisplayLayout.LEFT;
423 case SecondaryDisplayLayout.LEFT:
424 layoutToBase = SecondaryDisplayLayout.RIGHT;
426 case SecondaryDisplayLayout.TOP:
427 layoutToBase = SecondaryDisplayLayout.BOTTOM;
429 case SecondaryDisplayLayout.BOTTOM:
430 layoutToBase = SecondaryDisplayLayout.TOP;
435 switch (layoutToBase) {
436 case SecondaryDisplayLayout.RIGHT:
437 draggingDiv.style.left =
438 baseDiv.offsetLeft + baseDiv.offsetWidth + 'px';
439 draggingDiv.style.top = newPosition.y + 'px';
441 case SecondaryDisplayLayout.LEFT:
442 draggingDiv.style.left =
443 baseDiv.offsetLeft - draggingDiv.offsetWidth + 'px';
444 draggingDiv.style.top = newPosition.y + 'px';
446 case SecondaryDisplayLayout.TOP:
447 draggingDiv.style.top =
448 baseDiv.offsetTop - draggingDiv.offsetHeight + 'px';
449 draggingDiv.style.left = newPosition.x + 'px';
451 case SecondaryDisplayLayout.BOTTOM:
452 draggingDiv.style.top =
453 baseDiv.offsetTop + baseDiv.offsetHeight + 'px';
454 draggingDiv.style.left = newPosition.x + 'px';
462 * start dragging of a display rectangle.
463 * @param {HTMLElement} target The event target.
464 * @param {Object} eventLocation The object to hold the location where
465 * this event happens.
468 startDragging_: function(target, eventLocation) {
469 this.focusedIndex_ = null;
470 for (var i = 0; i < this.displays_.length; i++) {
471 var display = this.displays_[i];
472 if (display.div == target ||
473 (target.offsetParent && target.offsetParent == display.div)) {
474 this.focusedIndex_ = i;
479 for (var i = 0; i < this.displays_.length; i++) {
480 var display = this.displays_[i];
481 display.div.className = 'displays-display';
482 if (i != this.focusedIndex_)
485 display.div.classList.add('displays-focused');
486 if (this.displays_.length > 1) {
490 x: display.div.offsetLeft, y: display.div.offsetTop
492 eventLocation: eventLocation
497 this.updateSelectedDisplayDescription_();
502 * finish the current dragging of displays.
503 * @param {Event} e The event which triggers this.
506 endDragging_: function(e) {
507 this.lastTouchLocation_ = null;
508 if (this.dragging_) {
509 // Make sure the dragging location is connected.
510 var baseDiv = this.dragging_.display.isPrimary ?
511 this.secondaryDisplay_.div : this.primaryDisplay_.div;
512 var draggingDiv = this.dragging_.display.div;
513 if (this.layout_ == SecondaryDisplayLayout.LEFT ||
514 this.layout_ == SecondaryDisplayLayout.RIGHT) {
515 var top = Math.max(draggingDiv.offsetTop,
516 baseDiv.offsetTop - draggingDiv.offsetHeight +
519 baseDiv.offsetTop + baseDiv.offsetHeight -
521 draggingDiv.style.top = top + 'px';
523 var left = Math.max(draggingDiv.offsetLeft,
524 baseDiv.offsetLeft - draggingDiv.offsetWidth +
526 left = Math.min(left,
527 baseDiv.offsetLeft + baseDiv.offsetWidth -
529 draggingDiv.style.left = left + 'px';
531 var originalPosition = this.dragging_.display.originalPosition;
532 if (originalPosition.x != draggingDiv.offsetLeft ||
533 originalPosition.y != draggingDiv.offsetTop)
535 this.dragging_ = null;
537 this.updateSelectedDisplayDescription_();
542 * Updates the description of selected display section for mirroring mode.
545 updateSelectedDisplaySectionMirroring_: function() {
546 $('display-configuration-arrow').hidden = true;
547 $('display-options-set-primary').disabled = true;
548 $('display-options-toggle-mirroring').disabled = false;
549 $('selected-display-start-calibrating-overscan').disabled = true;
550 $('display-options-orientation-selection').disabled = true;
551 var display = this.displays_[0];
552 $('selected-display-name').textContent =
553 loadTimeData.getString('mirroringDisplay');
554 var resolution = $('display-options-resolution-selection');
555 var option = document.createElement('option');
556 option.value = 'default';
557 option.textContent = display.width + 'x' + display.height;
558 resolution.appendChild(option);
559 resolution.disabled = true;
563 * Updates the description of selected display section when no display is
567 updateSelectedDisplaySectionNoSelected_: function() {
568 $('display-configuration-arrow').hidden = true;
569 $('display-options-set-primary').disabled = true;
570 $('display-options-toggle-mirroring').disabled = true;
571 $('selected-display-start-calibrating-overscan').disabled = true;
572 $('display-options-orientation-selection').disabled = true;
573 $('selected-display-name').textContent = '';
574 var resolution = $('display-options-resolution-selection');
575 resolution.appendChild(document.createElement('option'));
576 resolution.disabled = true;
580 * Updates the description of selected display section for the selected
582 * @param {Object} display The selected display object.
585 updateSelectedDisplaySectionForDisplay_: function(display) {
586 var arrow = $('display-configuration-arrow');
587 arrow.hidden = false;
588 // Adding 1 px to the position to fit the border line and the border in
590 arrow.style.top = $('display-configurations').offsetTop -
591 arrow.offsetHeight / 2 + 'px';
592 arrow.style.left = display.div.offsetLeft +
593 display.div.offsetWidth / 2 - arrow.offsetWidth / 2 + 'px';
595 $('display-options-set-primary').disabled = display.isPrimary;
596 $('display-options-toggle-mirroring').disabled =
597 (this.displays_.length <= 1);
598 $('selected-display-start-calibrating-overscan').disabled =
601 var orientation = $('display-options-orientation-selection');
602 orientation.disabled = false;
603 var orientationOptions = orientation.getElementsByTagName('option');
604 orientationOptions[display.orientation].selected = true;
606 $('selected-display-name').textContent = display.name;
608 var resolution = $('display-options-resolution-selection');
609 if (display.resolutions.length <= 1) {
610 var option = document.createElement('option');
611 option.value = 'default';
612 option.textContent = display.width + 'x' + display.height;
613 option.selected = true;
614 resolution.appendChild(option);
615 resolution.disabled = true;
617 for (var i = 0; i < display.resolutions.length; i++) {
618 var option = document.createElement('option');
620 option.textContent = display.resolutions[i].width + 'x' +
621 display.resolutions[i].height;
622 if (display.resolutions[i].isBest) {
623 option.textContent += ' ' +
624 loadTimeData.getString('annotateBest');
626 option.selected = display.resolutions[i].selected;
627 resolution.appendChild(option);
629 resolution.disabled = (display.resolutions.length <= 1);
634 * Updates the description of the selected display section.
637 updateSelectedDisplayDescription_: function() {
638 var resolution = $('display-options-resolution-selection');
639 resolution.textContent = '';
640 var orientation = $('display-options-orientation-selection');
641 var orientationOptions = orientation.getElementsByTagName('option');
642 for (var i = 0; i < orientationOptions.length; i++)
643 orientationOptions.selected = false;
645 if (this.mirroring_) {
646 this.updateSelectedDisplaySectionMirroring_();
647 } else if (this.focusedIndex_ == null ||
648 this.displays_[this.focusedIndex_] == null) {
649 this.updateSelectedDisplaySectionNoSelected_();
651 this.updateSelectedDisplaySectionForDisplay_(
652 this.displays_[this.focusedIndex_]);
657 * Clears the drawing area for display rectangles.
660 resetDisplaysView_: function() {
661 var displaysViewHost = $('display-options-displays-view-host');
662 displaysViewHost.removeChild(displaysViewHost.firstChild);
663 this.displaysView_ = document.createElement('div');
664 this.displaysView_.id = 'display-options-displays-view';
665 displaysViewHost.appendChild(this.displaysView_);
669 * Lays out the display rectangles for mirroring.
672 layoutMirroringDisplays_: function() {
673 // Offset pixels for secondary display rectangles. The offset includes the
675 /** @const */ var MIRRORING_OFFSET_PIXELS = 3;
676 // Always show two displays because there must be two displays when
677 // the display_options is enabled. Don't rely on displays_.length because
678 // there is only one display from chrome's perspective in mirror mode.
679 /** @const */ var MIN_NUM_DISPLAYS = 2;
680 /** @const */ var MIRRORING_VERTICAL_MARGIN = 20;
682 // The width/height should be same as the first display:
683 var width = Math.ceil(this.displays_[0].width * this.visualScale_);
684 var height = Math.ceil(this.displays_[0].height * this.visualScale_);
686 var numDisplays = Math.max(MIN_NUM_DISPLAYS, this.displays_.length);
688 var totalWidth = width + numDisplays * MIRRORING_OFFSET_PIXELS;
689 var totalHeight = height + numDisplays * MIRRORING_OFFSET_PIXELS;
691 this.displaysView_.style.height = totalHeight + 'px';
692 this.displaysView_.classList.add(
693 'display-options-displays-view-mirroring');
695 // The displays should be centered.
697 $('display-options-displays-view').offsetWidth / 2 - totalWidth / 2;
699 for (var i = 0; i < numDisplays; i++) {
700 var div = document.createElement('div');
701 div.className = 'displays-display';
702 div.style.top = i * MIRRORING_OFFSET_PIXELS + 'px';
703 div.style.left = i * MIRRORING_OFFSET_PIXELS + offsetX + 'px';
704 div.style.width = width + 'px';
705 div.style.height = height + 'px';
706 div.style.zIndex = i;
707 // set 'display-mirrored' class for the background display rectangles.
708 if (i != numDisplays - 1)
709 div.classList.add('display-mirrored');
710 this.displaysView_.appendChild(div);
715 * Layouts the display rectangles according to the current layout_.
718 layoutDisplays_: function() {
721 var boundingBox = {left: 0, right: 0, top: 0, bottom: 0};
722 for (var i = 0; i < this.displays_.length; i++) {
723 var display = this.displays_[i];
724 boundingBox.left = Math.min(boundingBox.left, display.x);
725 boundingBox.right = Math.max(
726 boundingBox.right, display.x + display.width);
727 boundingBox.top = Math.min(boundingBox.top, display.y);
728 boundingBox.bottom = Math.max(
729 boundingBox.bottom, display.y + display.height);
730 maxWidth = Math.max(maxWidth, display.width);
731 maxHeight = Math.max(maxHeight, display.height);
734 // Make the margin around the bounding box.
735 var areaWidth = boundingBox.right - boundingBox.left + maxWidth;
736 var areaHeight = boundingBox.bottom - boundingBox.top + maxHeight;
738 // Calculates the scale by the width since horizontal size is more strict.
739 // TODO(mukai): Adds the check of vertical size in case.
740 this.visualScale_ = Math.min(
741 VISUAL_SCALE, this.displaysView_.offsetWidth / areaWidth);
743 // Prepare enough area for thisplays_view by adding the maximum height.
744 this.displaysView_.style.height =
745 Math.ceil(areaHeight * this.visualScale_) + 'px';
747 var boundingCenter = {
748 x: Math.floor((boundingBox.right + boundingBox.left) *
749 this.visualScale_ / 2),
750 y: Math.floor((boundingBox.bottom + boundingBox.top) *
751 this.visualScale_ / 2)
754 // Centering the bounding box of the display rectangles.
756 x: Math.floor(this.displaysView_.offsetWidth / 2 -
757 (boundingBox.right + boundingBox.left) * this.visualScale_ / 2),
758 y: Math.floor(this.displaysView_.offsetHeight / 2 -
759 (boundingBox.bottom + boundingBox.top) * this.visualScale_ / 2)
762 for (var i = 0; i < this.displays_.length; i++) {
763 var display = this.displays_[i];
764 var div = document.createElement('div');
767 div.className = 'displays-display';
768 if (i == this.focusedIndex_)
769 div.classList.add('displays-focused');
771 if (display.isPrimary) {
772 this.primaryDisplay_ = display;
774 this.secondaryDisplay_ = display;
776 var displayNameContainer = document.createElement('div');
777 displayNameContainer.textContent = display.name;
778 div.appendChild(displayNameContainer);
779 display.nameContainer = displayNameContainer;
780 display.div.style.width =
781 Math.floor(display.width * this.visualScale_) + 'px';
782 var newHeight = Math.floor(display.height * this.visualScale_);
783 display.div.style.height = newHeight + 'px';
785 Math.floor(display.x * this.visualScale_) + offset.x + 'px';
787 Math.floor(display.y * this.visualScale_) + offset.y + 'px';
788 display.nameContainer.style.marginTop =
789 (newHeight - display.nameContainer.offsetHeight) / 2 + 'px';
791 div.onmousedown = this.onMouseDown_.bind(this);
792 div.ontouchstart = this.onTouchStart_.bind(this);
794 this.displaysView_.appendChild(div);
796 // Set the margin top to place the display name at the middle of the
797 // rectangle. Note that this has to be done after it's added into the
798 // |displaysView_|. Otherwise its offsetHeight is yet 0.
799 displayNameContainer.style.marginTop =
800 (div.offsetHeight - displayNameContainer.offsetHeight) / 2 + 'px';
801 display.originalPosition = {x: div.offsetLeft, y: div.offsetTop};
806 * Called when the display arrangement has changed.
807 * @param {boolean} mirroring Whether current mode is mirroring or not.
808 * @param {Array} displays The list of the display information.
809 * @param {SecondaryDisplayLayout} layout The layout strategy.
810 * @param {number} offset The offset of the secondary display.
813 onDisplayChanged_: function(mirroring, displays, layout, offset) {
817 var hasExternal = false;
818 for (var i = 0; i < displays.length; i++) {
819 if (!displays[i].isInternal) {
825 this.layout_ = layout;
827 $('display-options-toggle-mirroring').textContent =
828 loadTimeData.getString(
829 mirroring ? 'stopMirroring' : 'startMirroring');
831 // Focus to the first display next to the primary one when |displays| list
834 this.focusedIndex_ = null;
835 } else if (this.mirroring_ != mirroring ||
836 this.displays_.length != displays.length) {
837 this.focusedIndex_ = 0;
840 this.mirroring_ = mirroring;
841 this.displays_ = displays;
843 this.resetDisplaysView_();
845 this.layoutMirroringDisplays_();
847 this.layoutDisplays_();
849 this.updateSelectedDisplayDescription_();
853 DisplayOptions.setDisplayInfo = function(
854 mirroring, displays, layout, offset) {
855 DisplayOptions.getInstance().onDisplayChanged_(
856 mirroring, displays, layout, offset);
861 DisplayOptions: DisplayOptions