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 Page = cr.ui.pageManager.Page;
7 var PageManager = cr.ui.pageManager.PageManager;
9 // The scale ratio of the display rectangle to its original size.
10 /** @const */ var VISUAL_SCALE = 1 / 10;
12 // The number of pixels to share the edges between displays.
13 /** @const */ var MIN_OFFSET_OVERLAP = 5;
16 * Enumeration of secondary display layout. The value has to be same as the
17 * values in ash/display/display_controller.cc.
20 var SecondaryDisplayLayout = {
28 * Calculates the bounds of |element| relative to the page.
29 * @param {HTMLElement} element The element to be known.
30 * @return {Object} The object for the bounds, with x, y, width, and height.
32 function getBoundsInPage(element) {
34 x: element.offsetLeft,
36 width: element.offsetWidth,
37 height: element.offsetHeight
39 var parent = element.offsetParent;
40 while (parent && parent != document.body) {
41 bounds.x += parent.offsetLeft;
42 bounds.y += parent.offsetTop;
43 parent = parent.offsetParent;
49 * Gets the position of |point| to |rect|, left, right, top, or bottom.
50 * @param {Object} rect The base rectangle with x, y, width, and height.
51 * @param {Object} point The point to check the position.
52 * @return {SecondaryDisplayLayout} The position of the calculated point.
54 function getPositionToRectangle(rect, point) {
55 // Separates the area into four (LEFT/RIGHT/TOP/BOTTOM) by the diagonals of
56 // the rect, and decides which area the display should reside.
57 var diagonalSlope = rect.height / rect.width;
58 var topDownIntercept = rect.y - rect.x * diagonalSlope;
59 var bottomUpIntercept = rect.y + rect.height + rect.x * diagonalSlope;
61 if (point.y > topDownIntercept + point.x * diagonalSlope) {
62 if (point.y > bottomUpIntercept - point.x * diagonalSlope)
63 return SecondaryDisplayLayout.BOTTOM;
65 return SecondaryDisplayLayout.LEFT;
67 if (point.y > bottomUpIntercept - point.x * diagonalSlope)
68 return SecondaryDisplayLayout.RIGHT;
70 return SecondaryDisplayLayout.TOP;
75 * Encapsulated handling of the 'Display' page.
78 function DisplayOptions() {
79 Page.call(this, 'display',
80 loadTimeData.getString('displayOptionsPageTabTitle'),
81 'display-options-page');
84 cr.addSingletonGetter(DisplayOptions);
86 DisplayOptions.prototype = {
87 __proto__: Page.prototype,
90 * Whether the current output status is mirroring displays or not.
96 * The current secondary display layout.
99 layout_: SecondaryDisplayLayout.RIGHT,
102 * The array of current output displays. It also contains the display
103 * rectangles currently rendered on screen.
109 * The index for the currently focused display in the options UI. null if
116 * The primary display.
119 primaryDisplay_: null,
122 * The secondary display.
125 secondaryDisplay_: null,
128 * The container div element which contains all of the display rectangles.
134 * The scale factor of the actual display size to the drawn display
138 visualScale_: VISUAL_SCALE,
141 * The location where the last touch event happened. This is used to
142 * prevent unnecessary dragging events happen. Set to null unless it's
143 * during touch events.
146 lastTouchLocation_: null,
149 initializePage: function() {
150 Page.prototype.initializePage.call(this);
152 $('display-options-toggle-mirroring').onclick = (function() {
153 this.mirroring_ = !this.mirroring_;
154 chrome.send('setMirroring', [this.mirroring_]);
157 var container = $('display-options-displays-view-host');
158 container.onmousemove = this.onMouseMove_.bind(this);
159 window.addEventListener('mouseup', this.endDragging_.bind(this), true);
160 container.ontouchmove = this.onTouchMove_.bind(this);
161 container.ontouchend = this.endDragging_.bind(this);
163 $('display-options-set-primary').onclick = (function() {
164 chrome.send('setPrimary', [this.displays_[this.focusedIndex_].id]);
166 $('display-options-resolution-selection').onchange = (function(ev) {
167 var display = this.displays_[this.focusedIndex_];
168 var resolution = display.resolutions[ev.target.value];
169 chrome.send('setDisplayMode', [display.id, resolution]);
171 $('display-options-orientation-selection').onchange = (function(ev) {
172 chrome.send('setOrientation', [this.displays_[this.focusedIndex_].id,
175 $('display-options-color-profile-selection').onchange = (function(ev) {
176 chrome.send('setColorProfile', [this.displays_[this.focusedIndex_].id,
179 $('selected-display-start-calibrating-overscan').onclick = (function() {
180 // Passes the target display ID. Do not specify it through URL hash,
181 // we do not care back/forward.
182 var displayOverscan = options.DisplayOverscan.getInstance();
183 displayOverscan.setDisplayId(this.displays_[this.focusedIndex_].id);
184 PageManager.showPageByName('displayOverscan');
185 chrome.send('coreOptionsUserMetricsAction',
186 ['Options_DisplaySetOverscan']);
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';
201 chrome.send('getDisplayInfo');
205 * Mouse move handler for dragging display rectangle.
206 * @param {Event} e The mouse move event.
209 onMouseMove_: function(e) {
210 return this.processDragging_(e, {x: e.pageX, y: e.pageY});
214 * Touch move handler for dragging display rectangle.
215 * @param {Event} e The touch move event.
218 onTouchMove_: function(e) {
219 if (e.touches.length != 1)
222 var touchLocation = {x: e.touches[0].pageX, y: e.touches[0].pageY};
223 // Touch move events happen even if the touch location doesn't change, but
224 // it doesn't need to process the dragging. Since sometimes the touch
225 // position changes slightly even though the user doesn't think to move
226 // the finger, very small move is just ignored.
227 /** @const */ var IGNORABLE_TOUCH_MOVE_PX = 1;
228 var xDiff = Math.abs(touchLocation.x - this.lastTouchLocation_.x);
229 var yDiff = Math.abs(touchLocation.y - this.lastTouchLocation_.y);
230 if (xDiff <= IGNORABLE_TOUCH_MOVE_PX &&
231 yDiff <= IGNORABLE_TOUCH_MOVE_PX) {
235 this.lastTouchLocation_ = touchLocation;
236 return this.processDragging_(e, touchLocation);
240 * Mouse down handler for dragging display rectangle.
241 * @param {Event} e The mouse down event.
244 onMouseDown_: function(e) {
252 return this.startDragging_(e.target, {x: e.pageX, y: e.pageY});
256 * Touch start handler for dragging display rectangle.
257 * @param {Event} e The touch start event.
260 onTouchStart_: function(e) {
264 if (e.touches.length != 1)
268 var touch = e.touches[0];
269 this.lastTouchLocation_ = {x: touch.pageX, y: touch.pageY};
270 return this.startDragging_(e.target, this.lastTouchLocation_);
274 * Collects the current data and sends it to Chrome.
277 applyResult_: function() {
278 // Offset is calculated from top or left edge.
279 var primary = this.primaryDisplay_;
280 var secondary = this.secondaryDisplay_;
282 if (this.layout_ == SecondaryDisplayLayout.LEFT ||
283 this.layout_ == SecondaryDisplayLayout.RIGHT) {
284 offset = secondary.div.offsetTop - primary.div.offsetTop;
286 offset = secondary.div.offsetLeft - primary.div.offsetLeft;
288 chrome.send('setDisplayLayout',
289 [this.layout_, offset / this.visualScale_]);
293 * Snaps the region [point, width] to [basePoint, baseWidth] if
294 * the [point, width] is close enough to the base's edge.
295 * @param {number} point The starting point of the region.
296 * @param {number} width The width of the region.
297 * @param {number} basePoint The starting point of the base region.
298 * @param {number} baseWidth The width of the base region.
299 * @return {number} The moved point. Returns point itself if it doesn't
300 * need to snap to the edge.
303 snapToEdge_: function(point, width, basePoint, baseWidth) {
304 // If the edge of the regions is smaller than this, it will snap to the
306 /** @const */ var SNAP_DISTANCE_PX = 16;
308 var startDiff = Math.abs(point - basePoint);
309 var endDiff = Math.abs(point + width - (basePoint + baseWidth));
310 // Prefer the closer one if both edges are close enough.
311 if (startDiff < SNAP_DISTANCE_PX && startDiff < endDiff)
313 else if (endDiff < SNAP_DISTANCE_PX)
314 return basePoint + baseWidth - width;
320 * Processes the actual dragging of display rectangle.
321 * @param {Event} e The event which triggers this drag.
322 * @param {Object} eventLocation The location where the event happens.
325 processDragging_: function(e, eventLocation) {
330 for (var i = 0; i < this.displays_.length; i++) {
331 if (this.displays_[i] == this.dragging_.display) {
341 // Note that current code of moving display-rectangles doesn't work
342 // if there are >=3 displays. This is our assumption for M21.
343 // TODO(mukai): Fix the code to allow >=3 displays.
345 x: this.dragging_.originalLocation.x +
346 (eventLocation.x - this.dragging_.eventLocation.x),
347 y: this.dragging_.originalLocation.y +
348 (eventLocation.y - this.dragging_.eventLocation.y)
351 var baseDiv = this.dragging_.display.isPrimary ?
352 this.secondaryDisplay_.div : this.primaryDisplay_.div;
353 var draggingDiv = this.dragging_.display.div;
355 newPosition.x = this.snapToEdge_(newPosition.x, draggingDiv.offsetWidth,
356 baseDiv.offsetLeft, baseDiv.offsetWidth);
357 newPosition.y = this.snapToEdge_(newPosition.y, draggingDiv.offsetHeight,
358 baseDiv.offsetTop, baseDiv.offsetHeight);
361 x: newPosition.x + draggingDiv.offsetWidth / 2,
362 y: newPosition.y + draggingDiv.offsetHeight / 2
366 x: baseDiv.offsetLeft,
367 y: baseDiv.offsetTop,
368 width: baseDiv.offsetWidth,
369 height: baseDiv.offsetHeight
371 switch (getPositionToRectangle(baseBounds, newCenter)) {
372 case SecondaryDisplayLayout.RIGHT:
373 this.layout_ = this.dragging_.display.isPrimary ?
374 SecondaryDisplayLayout.LEFT : SecondaryDisplayLayout.RIGHT;
376 case SecondaryDisplayLayout.LEFT:
377 this.layout_ = this.dragging_.display.isPrimary ?
378 SecondaryDisplayLayout.RIGHT : SecondaryDisplayLayout.LEFT;
380 case SecondaryDisplayLayout.TOP:
381 this.layout_ = this.dragging_.display.isPrimary ?
382 SecondaryDisplayLayout.BOTTOM : SecondaryDisplayLayout.TOP;
384 case SecondaryDisplayLayout.BOTTOM:
385 this.layout_ = this.dragging_.display.isPrimary ?
386 SecondaryDisplayLayout.TOP : SecondaryDisplayLayout.BOTTOM;
390 if (this.layout_ == SecondaryDisplayLayout.LEFT ||
391 this.layout_ == SecondaryDisplayLayout.RIGHT) {
392 if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight)
393 this.layout_ = this.dragging_.display.isPrimary ?
394 SecondaryDisplayLayout.TOP : SecondaryDisplayLayout.BOTTOM;
395 else if (newPosition.y + draggingDiv.offsetHeight <
397 this.layout_ = this.dragging_.display.isPrimary ?
398 SecondaryDisplayLayout.BOTTOM : SecondaryDisplayLayout.TOP;
400 if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth)
401 this.layout_ = this.dragging_.display.isPrimary ?
402 SecondaryDisplayLayout.LEFT : SecondaryDisplayLayout.RIGHT;
403 else if (newPosition.x + draggingDiv.offsetWidth <
405 this.layout_ = this.dragging_.display.isPrimary ?
406 SecondaryDisplayLayout.RIGHT : SecondaryDisplayLayout.LEFT;
410 if (!this.dragging_.display.isPrimary) {
411 layoutToBase = this.layout_;
413 switch (this.layout_) {
414 case SecondaryDisplayLayout.RIGHT:
415 layoutToBase = SecondaryDisplayLayout.LEFT;
417 case SecondaryDisplayLayout.LEFT:
418 layoutToBase = SecondaryDisplayLayout.RIGHT;
420 case SecondaryDisplayLayout.TOP:
421 layoutToBase = SecondaryDisplayLayout.BOTTOM;
423 case SecondaryDisplayLayout.BOTTOM:
424 layoutToBase = SecondaryDisplayLayout.TOP;
429 switch (layoutToBase) {
430 case SecondaryDisplayLayout.RIGHT:
431 draggingDiv.style.left =
432 baseDiv.offsetLeft + baseDiv.offsetWidth + 'px';
433 draggingDiv.style.top = newPosition.y + 'px';
435 case SecondaryDisplayLayout.LEFT:
436 draggingDiv.style.left =
437 baseDiv.offsetLeft - draggingDiv.offsetWidth + 'px';
438 draggingDiv.style.top = newPosition.y + 'px';
440 case SecondaryDisplayLayout.TOP:
441 draggingDiv.style.top =
442 baseDiv.offsetTop - draggingDiv.offsetHeight + 'px';
443 draggingDiv.style.left = newPosition.x + 'px';
445 case SecondaryDisplayLayout.BOTTOM:
446 draggingDiv.style.top =
447 baseDiv.offsetTop + baseDiv.offsetHeight + 'px';
448 draggingDiv.style.left = newPosition.x + 'px';
456 * start dragging of a display rectangle.
457 * @param {HTMLElement} target The event target.
458 * @param {Object} eventLocation The object to hold the location where
459 * this event happens.
462 startDragging_: function(target, eventLocation) {
463 this.focusedIndex_ = null;
464 for (var i = 0; i < this.displays_.length; i++) {
465 var display = this.displays_[i];
466 if (display.div == target ||
467 (target.offsetParent && target.offsetParent == display.div)) {
468 this.focusedIndex_ = i;
473 for (var i = 0; i < this.displays_.length; i++) {
474 var display = this.displays_[i];
475 display.div.className = 'displays-display';
476 if (i != this.focusedIndex_)
479 display.div.classList.add('displays-focused');
480 if (this.displays_.length > 1) {
484 x: display.div.offsetLeft, y: display.div.offsetTop
486 eventLocation: eventLocation
491 this.updateSelectedDisplayDescription_();
496 * finish the current dragging of displays.
497 * @param {Event} e The event which triggers this.
500 endDragging_: function(e) {
501 this.lastTouchLocation_ = null;
502 if (this.dragging_) {
503 // Make sure the dragging location is connected.
504 var baseDiv = this.dragging_.display.isPrimary ?
505 this.secondaryDisplay_.div : this.primaryDisplay_.div;
506 var draggingDiv = this.dragging_.display.div;
507 if (this.layout_ == SecondaryDisplayLayout.LEFT ||
508 this.layout_ == SecondaryDisplayLayout.RIGHT) {
509 var top = Math.max(draggingDiv.offsetTop,
510 baseDiv.offsetTop - draggingDiv.offsetHeight +
513 baseDiv.offsetTop + baseDiv.offsetHeight -
515 draggingDiv.style.top = top + 'px';
517 var left = Math.max(draggingDiv.offsetLeft,
518 baseDiv.offsetLeft - draggingDiv.offsetWidth +
520 left = Math.min(left,
521 baseDiv.offsetLeft + baseDiv.offsetWidth -
523 draggingDiv.style.left = left + 'px';
525 var originalPosition = this.dragging_.display.originalPosition;
526 if (originalPosition.x != draggingDiv.offsetLeft ||
527 originalPosition.y != draggingDiv.offsetTop)
529 this.dragging_ = null;
531 this.updateSelectedDisplayDescription_();
536 * Updates the description of selected display section for mirroring mode.
539 updateSelectedDisplaySectionMirroring_: function() {
540 $('display-configuration-arrow').hidden = true;
541 $('display-options-set-primary').disabled = true;
542 $('display-options-toggle-mirroring').disabled = false;
543 $('selected-display-start-calibrating-overscan').disabled = true;
544 $('display-options-orientation-selection').disabled = true;
545 var display = this.displays_[0];
546 $('selected-display-name').textContent =
547 loadTimeData.getString('mirroringDisplay');
548 var resolution = $('display-options-resolution-selection');
549 var option = document.createElement('option');
550 option.value = 'default';
551 option.textContent = display.width + 'x' + display.height;
552 resolution.appendChild(option);
553 resolution.disabled = true;
557 * Updates the description of selected display section when no display is
561 updateSelectedDisplaySectionNoSelected_: function() {
562 $('display-configuration-arrow').hidden = true;
563 $('display-options-set-primary').disabled = true;
564 $('display-options-toggle-mirroring').disabled = true;
565 $('selected-display-start-calibrating-overscan').disabled = true;
566 $('display-options-orientation-selection').disabled = true;
567 $('selected-display-name').textContent = '';
568 var resolution = $('display-options-resolution-selection');
569 resolution.appendChild(document.createElement('option'));
570 resolution.disabled = true;
574 * Updates the description of selected display section for the selected
576 * @param {Object} display The selected display object.
579 updateSelectedDisplaySectionForDisplay_: function(display) {
580 var arrow = $('display-configuration-arrow');
581 arrow.hidden = false;
582 // Adding 1 px to the position to fit the border line and the border in
584 arrow.style.top = $('display-configurations').offsetTop -
585 arrow.offsetHeight / 2 + 'px';
586 arrow.style.left = display.div.offsetLeft +
587 display.div.offsetWidth / 2 - arrow.offsetWidth / 2 + 'px';
589 $('display-options-set-primary').disabled = display.isPrimary;
590 $('display-options-toggle-mirroring').disabled =
591 (this.displays_.length <= 1);
592 $('selected-display-start-calibrating-overscan').disabled =
595 var orientation = $('display-options-orientation-selection');
596 orientation.disabled = false;
597 var orientationOptions = orientation.getElementsByTagName('option');
598 orientationOptions[display.orientation].selected = true;
600 $('selected-display-name').textContent = display.name;
602 var resolution = $('display-options-resolution-selection');
603 if (display.resolutions.length <= 1) {
604 var option = document.createElement('option');
605 option.value = 'default';
606 option.textContent = display.width + 'x' + display.height;
607 option.selected = true;
608 resolution.appendChild(option);
609 resolution.disabled = true;
612 for (var i = 0; i < display.resolutions.length; i++) {
613 var option = document.createElement('option');
615 option.textContent = display.resolutions[i].width + 'x' +
616 display.resolutions[i].height;
617 if (display.resolutions[i].isBest) {
618 option.textContent += ' ' +
619 loadTimeData.getString('annotateBest');
621 if (display.resolutions[i].deviceScaleFactor && previousOption &&
622 previousOption.textContent == option.textContent) {
623 option.textContent +=
624 ' (' + display.resolutions[i].deviceScaleFactor + 'x)';
626 option.selected = display.resolutions[i].selected;
627 resolution.appendChild(option);
628 previousOption = option;
630 resolution.disabled = (display.resolutions.length <= 1);
633 if (display.availableColorProfiles.length <= 1) {
634 $('selected-display-color-profile-row').hidden = true;
636 $('selected-display-color-profile-row').hidden = false;
637 var profiles = $('display-options-color-profile-selection');
638 profiles.innerHTML = '';
639 for (var i = 0; i < display.availableColorProfiles.length; i++) {
640 var option = document.createElement('option');
641 var colorProfile = display.availableColorProfiles[i];
642 option.value = colorProfile.profileId;
643 option.textContent = colorProfile.name;
645 display.colorProfile == colorProfile.profileId);
646 profiles.appendChild(option);
652 * Updates the description of the selected display section.
655 updateSelectedDisplayDescription_: function() {
656 var resolution = $('display-options-resolution-selection');
657 resolution.textContent = '';
658 var orientation = $('display-options-orientation-selection');
659 var orientationOptions = orientation.getElementsByTagName('option');
660 for (var i = 0; i < orientationOptions.length; i++)
661 orientationOptions.selected = false;
663 if (this.mirroring_) {
664 this.updateSelectedDisplaySectionMirroring_();
665 } else if (this.focusedIndex_ == null ||
666 this.displays_[this.focusedIndex_] == null) {
667 this.updateSelectedDisplaySectionNoSelected_();
669 this.updateSelectedDisplaySectionForDisplay_(
670 this.displays_[this.focusedIndex_]);
675 * Clears the drawing area for display rectangles.
678 resetDisplaysView_: function() {
679 var displaysViewHost = $('display-options-displays-view-host');
680 displaysViewHost.removeChild(displaysViewHost.firstChild);
681 this.displaysView_ = document.createElement('div');
682 this.displaysView_.id = 'display-options-displays-view';
683 displaysViewHost.appendChild(this.displaysView_);
687 * Lays out the display rectangles for mirroring.
690 layoutMirroringDisplays_: function() {
691 // Offset pixels for secondary display rectangles. The offset includes the
693 /** @const */ var MIRRORING_OFFSET_PIXELS = 3;
694 // Always show two displays because there must be two displays when
695 // the display_options is enabled. Don't rely on displays_.length because
696 // there is only one display from chrome's perspective in mirror mode.
697 /** @const */ var MIN_NUM_DISPLAYS = 2;
698 /** @const */ var MIRRORING_VERTICAL_MARGIN = 20;
700 // The width/height should be same as the first display:
701 var width = Math.ceil(this.displays_[0].width * this.visualScale_);
702 var height = Math.ceil(this.displays_[0].height * this.visualScale_);
704 var numDisplays = Math.max(MIN_NUM_DISPLAYS, this.displays_.length);
706 var totalWidth = width + numDisplays * MIRRORING_OFFSET_PIXELS;
707 var totalHeight = height + numDisplays * MIRRORING_OFFSET_PIXELS;
709 this.displaysView_.style.height = totalHeight + 'px';
710 this.displaysView_.classList.add(
711 'display-options-displays-view-mirroring');
713 // The displays should be centered.
715 $('display-options-displays-view').offsetWidth / 2 - totalWidth / 2;
717 for (var i = 0; i < numDisplays; i++) {
718 var div = document.createElement('div');
719 div.className = 'displays-display';
720 div.style.top = i * MIRRORING_OFFSET_PIXELS + 'px';
721 div.style.left = i * MIRRORING_OFFSET_PIXELS + offsetX + 'px';
722 div.style.width = width + 'px';
723 div.style.height = height + 'px';
724 div.style.zIndex = i;
725 // set 'display-mirrored' class for the background display rectangles.
726 if (i != numDisplays - 1)
727 div.classList.add('display-mirrored');
728 this.displaysView_.appendChild(div);
733 * Layouts the display rectangles according to the current layout_.
736 layoutDisplays_: function() {
739 var boundingBox = {left: 0, right: 0, top: 0, bottom: 0};
740 for (var i = 0; i < this.displays_.length; i++) {
741 var display = this.displays_[i];
742 boundingBox.left = Math.min(boundingBox.left, display.x);
743 boundingBox.right = Math.max(
744 boundingBox.right, display.x + display.width);
745 boundingBox.top = Math.min(boundingBox.top, display.y);
746 boundingBox.bottom = Math.max(
747 boundingBox.bottom, display.y + display.height);
748 maxWidth = Math.max(maxWidth, display.width);
749 maxHeight = Math.max(maxHeight, display.height);
752 // Make the margin around the bounding box.
753 var areaWidth = boundingBox.right - boundingBox.left + maxWidth;
754 var areaHeight = boundingBox.bottom - boundingBox.top + maxHeight;
756 // Calculates the scale by the width since horizontal size is more strict.
757 // TODO(mukai): Adds the check of vertical size in case.
758 this.visualScale_ = Math.min(
759 VISUAL_SCALE, this.displaysView_.offsetWidth / areaWidth);
761 // Prepare enough area for thisplays_view by adding the maximum height.
762 this.displaysView_.style.height =
763 Math.ceil(areaHeight * this.visualScale_) + 'px';
765 var boundingCenter = {
766 x: Math.floor((boundingBox.right + boundingBox.left) *
767 this.visualScale_ / 2),
768 y: Math.floor((boundingBox.bottom + boundingBox.top) *
769 this.visualScale_ / 2)
772 // Centering the bounding box of the display rectangles.
774 x: Math.floor(this.displaysView_.offsetWidth / 2 -
775 (boundingBox.right + boundingBox.left) * this.visualScale_ / 2),
776 y: Math.floor(this.displaysView_.offsetHeight / 2 -
777 (boundingBox.bottom + boundingBox.top) * this.visualScale_ / 2)
780 for (var i = 0; i < this.displays_.length; i++) {
781 var display = this.displays_[i];
782 var div = document.createElement('div');
785 div.className = 'displays-display';
786 if (i == this.focusedIndex_)
787 div.classList.add('displays-focused');
789 if (display.isPrimary) {
790 this.primaryDisplay_ = display;
792 this.secondaryDisplay_ = display;
794 var displayNameContainer = document.createElement('div');
795 displayNameContainer.textContent = display.name;
796 div.appendChild(displayNameContainer);
797 display.nameContainer = displayNameContainer;
798 display.div.style.width =
799 Math.floor(display.width * this.visualScale_) + 'px';
800 var newHeight = Math.floor(display.height * this.visualScale_);
801 display.div.style.height = newHeight + 'px';
803 Math.floor(display.x * this.visualScale_) + offset.x + 'px';
805 Math.floor(display.y * this.visualScale_) + offset.y + 'px';
806 display.nameContainer.style.marginTop =
807 (newHeight - display.nameContainer.offsetHeight) / 2 + 'px';
809 div.onmousedown = this.onMouseDown_.bind(this);
810 div.ontouchstart = this.onTouchStart_.bind(this);
812 this.displaysView_.appendChild(div);
814 // Set the margin top to place the display name at the middle of the
815 // rectangle. Note that this has to be done after it's added into the
816 // |displaysView_|. Otherwise its offsetHeight is yet 0.
817 displayNameContainer.style.marginTop =
818 (div.offsetHeight - displayNameContainer.offsetHeight) / 2 + 'px';
819 display.originalPosition = {x: div.offsetLeft, y: div.offsetTop};
824 * Called when the display arrangement has changed.
825 * @param {boolean} mirroring Whether current mode is mirroring or not.
826 * @param {Array} displays The list of the display information.
827 * @param {SecondaryDisplayLayout} layout The layout strategy.
828 * @param {number} offset The offset of the secondary display.
831 onDisplayChanged_: function(mirroring, displays, layout, offset) {
835 var hasExternal = false;
836 for (var i = 0; i < displays.length; i++) {
837 if (!displays[i].isInternal) {
843 this.layout_ = layout;
845 $('display-options-toggle-mirroring').textContent =
846 loadTimeData.getString(
847 mirroring ? 'stopMirroring' : 'startMirroring');
849 // Focus to the first display next to the primary one when |displays| list
852 this.focusedIndex_ = null;
853 } else if (this.mirroring_ != mirroring ||
854 this.displays_.length != displays.length) {
855 this.focusedIndex_ = 0;
858 this.mirroring_ = mirroring;
859 this.displays_ = displays;
861 this.resetDisplaysView_();
863 this.layoutMirroringDisplays_();
865 this.layoutDisplays_();
867 this.updateSelectedDisplayDescription_();
871 DisplayOptions.setDisplayInfo = function(
872 mirroring, displays, layout, offset) {
873 DisplayOptions.getInstance().onDisplayChanged_(
874 mirroring, displays, layout, offset);
879 DisplayOptions: DisplayOptions