Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / resources / options / chromeos / display_options.js
blob3eeaf0a80a343e601d4e0a2c4164f5df30de3b61
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');
7 /**
8 * @typedef {{
9 * availableColorProfiles: Array<{profileId: number, name: string}>,
10 * colorProfile: number,
11 * height: number,
12 * id: string,
13 * isInternal: boolean,
14 * isPrimary: boolean,
15 * resolutions: Array<{width: number, height: number, originalWidth: number,
16 * originalHeight: number, deviceScaleFactor: number, scale: number,
17 * refreshRate: number, isBest: boolean, selected: boolean}>,
18 * name: string,
19 * orientation: number,
20 * width: number,
21 * x: number,
22 * y: number
23 * }}
25 options.DisplayInfo;
27 /**
28 * Enumeration of secondary display layout. The value has to be same as the
29 * values in ash/display/display_controller.cc.
30 * @enum {number}
32 options.SecondaryDisplayLayout = {
33 TOP: 0,
34 RIGHT: 1,
35 BOTTOM: 2,
36 LEFT: 3
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;
49 /**
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) {
55 var bounds = {
56 x: element.offsetLeft,
57 y: element.offsetTop,
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;
67 return bounds;
70 /**
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
75 * point.
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;
87 else
88 return options.SecondaryDisplayLayout.LEFT;
89 } else {
90 if (point.y > bottomUpIntercept - point.x * diagonalSlope)
91 return options.SecondaryDisplayLayout.RIGHT;
92 else
93 return options.SecondaryDisplayLayout.TOP;
97 /**
98 * Encapsulated handling of the 'Display' page.
99 * @constructor
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.
115 * @private
117 mirroring_: false,
120 * The current secondary display layout.
121 * @private
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>}
129 * @private
131 displays_: [],
134 * The index for the currently focused display in the options UI. null if
135 * no one has focus.
136 * @private
138 focusedIndex_: null,
141 * The primary display.
142 * @private
144 primaryDisplay_: null,
147 * The secondary display.
148 * @private
150 secondaryDisplay_: null,
153 * The container div element which contains all of the display rectangles.
154 * @private
156 displaysView_: null,
159 * The scale factor of the actual display size to the drawn display
160 * rectangle size.
161 * @private
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.
169 * @private
171 lastTouchLocation_: null,
174 * Whether the display settings can be shown.
175 * @private
177 enabled_: true,
179 /** @override */
180 initializePage: function() {
181 Page.prototype.initializePage.call(this);
183 $('display-options-toggle-mirroring').onclick = (function() {
184 this.mirroring_ = !this.mirroring_;
185 chrome.send('setMirroring', [this.mirroring_]);
186 }).bind(this);
188 var container = $('display-options-displays-view-host');
189 container.onmousemove = this.onMouseMove_.bind(this);
190 window.addEventListener('mouseup', this.endDragging_.bind(this), true);
191 container.ontouchmove = this.onTouchMove_.bind(this);
192 container.ontouchend = this.endDragging_.bind(this);
194 $('display-options-set-primary').onclick = (function() {
195 chrome.send('setPrimary', [this.displays_[this.focusedIndex_].id]);
196 }).bind(this);
197 $('display-options-resolution-selection').onchange = (function(ev) {
198 var display = this.displays_[this.focusedIndex_];
199 var resolution = display.resolutions[ev.target.value];
200 chrome.send('setDisplayMode', [display.id, resolution]);
201 }).bind(this);
202 $('display-options-orientation-selection').onchange = (function(ev) {
203 chrome.send('setOrientation', [this.displays_[this.focusedIndex_].id,
204 ev.target.value]);
205 }).bind(this);
206 $('display-options-color-profile-selection').onchange = (function(ev) {
207 chrome.send('setColorProfile', [this.displays_[this.focusedIndex_].id,
208 ev.target.value]);
209 }).bind(this);
210 $('selected-display-start-calibrating-overscan').onclick = (function() {
211 // Passes the target display ID. Do not specify it through URL hash,
212 // we do not care back/forward.
213 var displayOverscan = options.DisplayOverscan.getInstance();
214 displayOverscan.setDisplayId(this.displays_[this.focusedIndex_].id);
215 PageManager.showPageByName('displayOverscan');
216 chrome.send('coreOptionsUserMetricsAction',
217 ['Options_DisplaySetOverscan']);
218 }).bind(this);
220 $('display-options-done').onclick = function() {
221 PageManager.closeOverlay();
225 /** @override */
226 didShowPage: function() {
227 var optionTitles = document.getElementsByClassName(
228 'selected-display-option-title');
229 var maxSize = 0;
230 for (var i = 0; i < optionTitles.length; i++)
231 maxSize = Math.max(maxSize, optionTitles[i].clientWidth);
232 for (var i = 0; i < optionTitles.length; i++)
233 optionTitles[i].style.width = maxSize + 'px';
234 chrome.send('getDisplayInfo');
237 /** @override */
238 canShowPage: function() {
239 return this.enabled_;
243 * Enables or disables the page. When disabled, the page will not be able to
244 * open, and will close if currently opened.
245 * @param {boolean} enabled Whether the page should be enabled.
247 setEnabled: function(enabled) {
248 if (this.enabled_ == enabled)
249 return;
250 this.enabled_ = enabled;
251 if (!enabled && this.visible)
252 PageManager.closeOverlay();
256 * Mouse move handler for dragging display rectangle.
257 * @param {Event} e The mouse move event.
258 * @private
260 onMouseMove_: function(e) {
261 return this.processDragging_(e, {x: e.pageX, y: e.pageY});
265 * Touch move handler for dragging display rectangle.
266 * @param {Event} e The touch move event.
267 * @private
269 onTouchMove_: function(e) {
270 if (e.touches.length != 1)
271 return true;
273 var touchLocation = {x: e.touches[0].pageX, y: e.touches[0].pageY};
274 // Touch move events happen even if the touch location doesn't change, but
275 // it doesn't need to process the dragging. Since sometimes the touch
276 // position changes slightly even though the user doesn't think to move
277 // the finger, very small move is just ignored.
278 /** @const */ var IGNORABLE_TOUCH_MOVE_PX = 1;
279 var xDiff = Math.abs(touchLocation.x - this.lastTouchLocation_.x);
280 var yDiff = Math.abs(touchLocation.y - this.lastTouchLocation_.y);
281 if (xDiff <= IGNORABLE_TOUCH_MOVE_PX &&
282 yDiff <= IGNORABLE_TOUCH_MOVE_PX) {
283 return true;
286 this.lastTouchLocation_ = touchLocation;
287 return this.processDragging_(e, touchLocation);
291 * Mouse down handler for dragging display rectangle.
292 * @param {Event} e The mouse down event.
293 * @private
295 onMouseDown_: function(e) {
296 if (this.mirroring_)
297 return true;
299 if (e.button != 0)
300 return true;
302 e.preventDefault();
303 var target = assertInstanceof(e.target, HTMLElement);
304 return this.startDragging_(target, {x: e.pageX, y: e.pageY});
308 * Touch start handler for dragging display rectangle.
309 * @param {Event} e The touch start event.
310 * @private
312 onTouchStart_: function(e) {
313 if (this.mirroring_)
314 return true;
316 if (e.touches.length != 1)
317 return false;
319 e.preventDefault();
320 var touch = e.touches[0];
321 this.lastTouchLocation_ = {x: touch.pageX, y: touch.pageY};
322 var target = assertInstanceof(e.target, HTMLElement);
323 return this.startDragging_(target, this.lastTouchLocation_);
327 * Collects the current data and sends it to Chrome.
328 * @private
330 applyResult_: function() {
331 // Offset is calculated from top or left edge.
332 var primary = this.primaryDisplay_;
333 var secondary = this.secondaryDisplay_;
334 var offset;
335 if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
336 this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
337 offset = secondary.div.offsetTop - primary.div.offsetTop;
338 } else {
339 offset = secondary.div.offsetLeft - primary.div.offsetLeft;
341 chrome.send('setDisplayLayout',
342 [this.layout_, offset / this.visualScale_]);
346 * Snaps the region [point, width] to [basePoint, baseWidth] if
347 * the [point, width] is close enough to the base's edge.
348 * @param {number} point The starting point of the region.
349 * @param {number} width The width of the region.
350 * @param {number} basePoint The starting point of the base region.
351 * @param {number} baseWidth The width of the base region.
352 * @return {number} The moved point. Returns point itself if it doesn't
353 * need to snap to the edge.
354 * @private
356 snapToEdge_: function(point, width, basePoint, baseWidth) {
357 // If the edge of the regions is smaller than this, it will snap to the
358 // base's edge.
359 /** @const */ var SNAP_DISTANCE_PX = 16;
361 var startDiff = Math.abs(point - basePoint);
362 var endDiff = Math.abs(point + width - (basePoint + baseWidth));
363 // Prefer the closer one if both edges are close enough.
364 if (startDiff < SNAP_DISTANCE_PX && startDiff < endDiff)
365 return basePoint;
366 else if (endDiff < SNAP_DISTANCE_PX)
367 return basePoint + baseWidth - width;
369 return point;
373 * Processes the actual dragging of display rectangle.
374 * @param {Event} e The event which triggers this drag.
375 * @param {Object} eventLocation The location where the event happens.
376 * @private
378 processDragging_: function(e, eventLocation) {
379 if (!this.dragging_)
380 return true;
382 var index = -1;
383 for (var i = 0; i < this.displays_.length; i++) {
384 if (this.displays_[i] == this.dragging_.display) {
385 index = i;
386 break;
389 if (index < 0)
390 return true;
392 e.preventDefault();
394 // Note that current code of moving display-rectangles doesn't work
395 // if there are >=3 displays. This is our assumption for M21.
396 // TODO(mukai): Fix the code to allow >=3 displays.
397 var newPosition = {
398 x: this.dragging_.originalLocation.x +
399 (eventLocation.x - this.dragging_.eventLocation.x),
400 y: this.dragging_.originalLocation.y +
401 (eventLocation.y - this.dragging_.eventLocation.y)
404 var baseDiv = this.dragging_.display.isPrimary ?
405 this.secondaryDisplay_.div : this.primaryDisplay_.div;
406 var draggingDiv = this.dragging_.display.div;
408 newPosition.x = this.snapToEdge_(newPosition.x, draggingDiv.offsetWidth,
409 baseDiv.offsetLeft, baseDiv.offsetWidth);
410 newPosition.y = this.snapToEdge_(newPosition.y, draggingDiv.offsetHeight,
411 baseDiv.offsetTop, baseDiv.offsetHeight);
413 var newCenter = {
414 x: newPosition.x + draggingDiv.offsetWidth / 2,
415 y: newPosition.y + draggingDiv.offsetHeight / 2
418 var baseBounds = {
419 x: baseDiv.offsetLeft,
420 y: baseDiv.offsetTop,
421 width: baseDiv.offsetWidth,
422 height: baseDiv.offsetHeight
424 switch (getPositionToRectangle(baseBounds, newCenter)) {
425 case options.SecondaryDisplayLayout.RIGHT:
426 this.layout_ = this.dragging_.display.isPrimary ?
427 options.SecondaryDisplayLayout.LEFT :
428 options.SecondaryDisplayLayout.RIGHT;
429 break;
430 case options.SecondaryDisplayLayout.LEFT:
431 this.layout_ = this.dragging_.display.isPrimary ?
432 options.SecondaryDisplayLayout.RIGHT :
433 options.SecondaryDisplayLayout.LEFT;
434 break;
435 case options.SecondaryDisplayLayout.TOP:
436 this.layout_ = this.dragging_.display.isPrimary ?
437 options.SecondaryDisplayLayout.BOTTOM :
438 options.SecondaryDisplayLayout.TOP;
439 break;
440 case options.SecondaryDisplayLayout.BOTTOM:
441 this.layout_ = this.dragging_.display.isPrimary ?
442 options.SecondaryDisplayLayout.TOP :
443 options.SecondaryDisplayLayout.BOTTOM;
444 break;
447 if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
448 this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
449 if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight)
450 this.layout_ = this.dragging_.display.isPrimary ?
451 options.SecondaryDisplayLayout.TOP :
452 options.SecondaryDisplayLayout.BOTTOM;
453 else if (newPosition.y + draggingDiv.offsetHeight <
454 baseDiv.offsetTop)
455 this.layout_ = this.dragging_.display.isPrimary ?
456 options.SecondaryDisplayLayout.BOTTOM :
457 options.SecondaryDisplayLayout.TOP;
458 } else {
459 if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth)
460 this.layout_ = this.dragging_.display.isPrimary ?
461 options.SecondaryDisplayLayout.LEFT :
462 options.SecondaryDisplayLayout.RIGHT;
463 else if (newPosition.x + draggingDiv.offsetWidth <
464 baseDiv.offsetLeft)
465 this.layout_ = this.dragging_.display.isPrimary ?
466 options.SecondaryDisplayLayout.RIGHT :
467 options.SecondaryDisplayLayout.LEFT;
470 var layoutToBase;
471 if (!this.dragging_.display.isPrimary) {
472 layoutToBase = this.layout_;
473 } else {
474 switch (this.layout_) {
475 case options.SecondaryDisplayLayout.RIGHT:
476 layoutToBase = options.SecondaryDisplayLayout.LEFT;
477 break;
478 case options.SecondaryDisplayLayout.LEFT:
479 layoutToBase = options.SecondaryDisplayLayout.RIGHT;
480 break;
481 case options.SecondaryDisplayLayout.TOP:
482 layoutToBase = options.SecondaryDisplayLayout.BOTTOM;
483 break;
484 case options.SecondaryDisplayLayout.BOTTOM:
485 layoutToBase = options.SecondaryDisplayLayout.TOP;
486 break;
490 switch (layoutToBase) {
491 case options.SecondaryDisplayLayout.RIGHT:
492 draggingDiv.style.left =
493 baseDiv.offsetLeft + baseDiv.offsetWidth + 'px';
494 draggingDiv.style.top = newPosition.y + 'px';
495 break;
496 case options.SecondaryDisplayLayout.LEFT:
497 draggingDiv.style.left =
498 baseDiv.offsetLeft - draggingDiv.offsetWidth + 'px';
499 draggingDiv.style.top = newPosition.y + 'px';
500 break;
501 case options.SecondaryDisplayLayout.TOP:
502 draggingDiv.style.top =
503 baseDiv.offsetTop - draggingDiv.offsetHeight + 'px';
504 draggingDiv.style.left = newPosition.x + 'px';
505 break;
506 case options.SecondaryDisplayLayout.BOTTOM:
507 draggingDiv.style.top =
508 baseDiv.offsetTop + baseDiv.offsetHeight + 'px';
509 draggingDiv.style.left = newPosition.x + 'px';
510 break;
513 return false;
517 * start dragging of a display rectangle.
518 * @param {HTMLElement} target The event target.
519 * @param {Object} eventLocation The object to hold the location where
520 * this event happens.
521 * @private
523 startDragging_: function(target, eventLocation) {
524 this.focusedIndex_ = null;
525 for (var i = 0; i < this.displays_.length; i++) {
526 var display = this.displays_[i];
527 if (display.div == target ||
528 (target.offsetParent && target.offsetParent == display.div)) {
529 this.focusedIndex_ = i;
530 break;
534 for (var i = 0; i < this.displays_.length; i++) {
535 var display = this.displays_[i];
536 display.div.className = 'displays-display';
537 if (i != this.focusedIndex_)
538 continue;
540 display.div.classList.add('displays-focused');
541 if (this.displays_.length > 1) {
542 this.dragging_ = {
543 display: display,
544 originalLocation: {
545 x: display.div.offsetLeft, y: display.div.offsetTop
547 eventLocation: eventLocation
552 this.updateSelectedDisplayDescription_();
553 return false;
557 * finish the current dragging of displays.
558 * @param {Event} e The event which triggers this.
559 * @private
561 endDragging_: function(e) {
562 this.lastTouchLocation_ = null;
563 if (this.dragging_) {
564 // Make sure the dragging location is connected.
565 var baseDiv = this.dragging_.display.isPrimary ?
566 this.secondaryDisplay_.div : this.primaryDisplay_.div;
567 var draggingDiv = this.dragging_.display.div;
568 if (this.layout_ == options.SecondaryDisplayLayout.LEFT ||
569 this.layout_ == options.SecondaryDisplayLayout.RIGHT) {
570 var top = Math.max(draggingDiv.offsetTop,
571 baseDiv.offsetTop - draggingDiv.offsetHeight +
572 MIN_OFFSET_OVERLAP);
573 top = Math.min(top,
574 baseDiv.offsetTop + baseDiv.offsetHeight -
575 MIN_OFFSET_OVERLAP);
576 draggingDiv.style.top = top + 'px';
577 } else {
578 var left = Math.max(draggingDiv.offsetLeft,
579 baseDiv.offsetLeft - draggingDiv.offsetWidth +
580 MIN_OFFSET_OVERLAP);
581 left = Math.min(left,
582 baseDiv.offsetLeft + baseDiv.offsetWidth -
583 MIN_OFFSET_OVERLAP);
584 draggingDiv.style.left = left + 'px';
586 var originalPosition = this.dragging_.display.originalPosition;
587 if (originalPosition.x != draggingDiv.offsetLeft ||
588 originalPosition.y != draggingDiv.offsetTop)
589 this.applyResult_();
590 this.dragging_ = null;
592 this.updateSelectedDisplayDescription_();
593 return false;
597 * Updates the description of selected display section for mirroring mode.
598 * @private
600 updateSelectedDisplaySectionMirroring_: function() {
601 $('display-configuration-arrow').hidden = true;
602 $('display-options-set-primary').disabled = true;
603 $('display-options-toggle-mirroring').disabled = false;
604 $('selected-display-start-calibrating-overscan').disabled = true;
605 $('display-options-orientation-selection').disabled = true;
606 var display = this.displays_[0];
607 $('selected-display-name').textContent =
608 loadTimeData.getString('mirroringDisplay');
609 var resolution = $('display-options-resolution-selection');
610 var option = document.createElement('option');
611 option.value = 'default';
612 option.textContent = display.width + 'x' + display.height;
613 resolution.appendChild(option);
614 resolution.disabled = true;
618 * Updates the description of selected display section when no display is
619 * selected.
620 * @private
622 updateSelectedDisplaySectionNoSelected_: function() {
623 $('display-configuration-arrow').hidden = true;
624 $('display-options-set-primary').disabled = true;
625 $('display-options-toggle-mirroring').disabled = true;
626 $('selected-display-start-calibrating-overscan').disabled = true;
627 $('display-options-orientation-selection').disabled = true;
628 $('selected-display-name').textContent = '';
629 var resolution = $('display-options-resolution-selection');
630 resolution.appendChild(document.createElement('option'));
631 resolution.disabled = true;
635 * Updates the description of selected display section for the selected
636 * display.
637 * @param {Object} display The selected display object.
638 * @private
640 updateSelectedDisplaySectionForDisplay_: function(display) {
641 var arrow = $('display-configuration-arrow');
642 arrow.hidden = false;
643 // Adding 1 px to the position to fit the border line and the border in
644 // arrow precisely.
645 arrow.style.top = $('display-configurations').offsetTop -
646 arrow.offsetHeight / 2 + 'px';
647 arrow.style.left = display.div.offsetLeft +
648 display.div.offsetWidth / 2 - arrow.offsetWidth / 2 + 'px';
650 $('display-options-set-primary').disabled = display.isPrimary;
651 $('display-options-toggle-mirroring').disabled =
652 (this.displays_.length <= 1);
653 $('selected-display-start-calibrating-overscan').disabled =
654 display.isInternal;
656 var orientation = $('display-options-orientation-selection');
657 orientation.disabled = false;
658 var orientationOptions = orientation.getElementsByTagName('option');
659 orientationOptions[display.orientation].selected = true;
661 $('selected-display-name').textContent = display.name;
663 var resolution = $('display-options-resolution-selection');
664 if (display.resolutions.length <= 1) {
665 var option = document.createElement('option');
666 option.value = 'default';
667 option.textContent = display.width + 'x' + display.height;
668 option.selected = true;
669 resolution.appendChild(option);
670 resolution.disabled = true;
671 } else {
672 var previousOption;
673 for (var i = 0; i < display.resolutions.length; i++) {
674 var option = document.createElement('option');
675 option.value = i;
676 option.textContent = display.resolutions[i].width + 'x' +
677 display.resolutions[i].height;
678 if (display.resolutions[i].isBest) {
679 option.textContent += ' ' +
680 loadTimeData.getString('annotateBest');
681 } else if (display.resolutions[i].isNative) {
682 option.textContent += ' ' +
683 loadTimeData.getString('annotateNative');
685 if (display.resolutions[i].deviceScaleFactor && previousOption &&
686 previousOption.textContent == option.textContent) {
687 option.textContent +=
688 ' (' + display.resolutions[i].deviceScaleFactor + 'x)';
690 option.selected = display.resolutions[i].selected;
691 resolution.appendChild(option);
692 previousOption = option;
694 resolution.disabled = (display.resolutions.length <= 1);
697 if (display.availableColorProfiles.length <= 1) {
698 $('selected-display-color-profile-row').hidden = true;
699 } else {
700 $('selected-display-color-profile-row').hidden = false;
701 var profiles = $('display-options-color-profile-selection');
702 profiles.innerHTML = '';
703 for (var i = 0; i < display.availableColorProfiles.length; i++) {
704 var option = document.createElement('option');
705 var colorProfile = display.availableColorProfiles[i];
706 option.value = colorProfile.profileId;
707 option.textContent = colorProfile.name;
708 option.selected = (
709 display.colorProfile == colorProfile.profileId);
710 profiles.appendChild(option);
716 * Updates the description of the selected display section.
717 * @private
719 updateSelectedDisplayDescription_: function() {
720 var resolution = $('display-options-resolution-selection');
721 resolution.textContent = '';
722 var orientation = $('display-options-orientation-selection');
723 var orientationOptions = orientation.getElementsByTagName('option');
724 for (var i = 0; i < orientationOptions.length; i++)
725 orientationOptions.selected = false;
727 if (this.mirroring_) {
728 this.updateSelectedDisplaySectionMirroring_();
729 } else if (this.focusedIndex_ == null ||
730 this.displays_[this.focusedIndex_] == null) {
731 this.updateSelectedDisplaySectionNoSelected_();
732 } else {
733 this.updateSelectedDisplaySectionForDisplay_(
734 this.displays_[this.focusedIndex_]);
739 * Clears the drawing area for display rectangles.
740 * @private
742 resetDisplaysView_: function() {
743 var displaysViewHost = $('display-options-displays-view-host');
744 displaysViewHost.removeChild(displaysViewHost.firstChild);
745 this.displaysView_ = document.createElement('div');
746 this.displaysView_.id = 'display-options-displays-view';
747 displaysViewHost.appendChild(this.displaysView_);
751 * Lays out the display rectangles for mirroring.
752 * @private
754 layoutMirroringDisplays_: function() {
755 // Offset pixels for secondary display rectangles. The offset includes the
756 // border width.
757 /** @const */ var MIRRORING_OFFSET_PIXELS = 3;
758 // Always show two displays because there must be two displays when
759 // the display_options is enabled. Don't rely on displays_.length because
760 // there is only one display from chrome's perspective in mirror mode.
761 /** @const */ var MIN_NUM_DISPLAYS = 2;
762 /** @const */ var MIRRORING_VERTICAL_MARGIN = 20;
764 // The width/height should be same as the first display:
765 var width = Math.ceil(this.displays_[0].width * this.visualScale_);
766 var height = Math.ceil(this.displays_[0].height * this.visualScale_);
768 var numDisplays = Math.max(MIN_NUM_DISPLAYS, this.displays_.length);
770 var totalWidth = width + numDisplays * MIRRORING_OFFSET_PIXELS;
771 var totalHeight = height + numDisplays * MIRRORING_OFFSET_PIXELS;
773 this.displaysView_.style.height = totalHeight + 'px';
775 // The displays should be centered.
776 var offsetX =
777 $('display-options-displays-view').offsetWidth / 2 - totalWidth / 2;
779 for (var i = 0; i < numDisplays; i++) {
780 var div = document.createElement('div');
781 div.className = 'displays-display';
782 div.style.top = i * MIRRORING_OFFSET_PIXELS + 'px';
783 div.style.left = i * MIRRORING_OFFSET_PIXELS + offsetX + 'px';
784 div.style.width = width + 'px';
785 div.style.height = height + 'px';
786 div.style.zIndex = i;
787 // set 'display-mirrored' class for the background display rectangles.
788 if (i != numDisplays - 1)
789 div.classList.add('display-mirrored');
790 this.displaysView_.appendChild(div);
795 * Creates a div element representing the specified display.
796 * @param {Object} display The display object.
797 * @param {boolean} focused True if it's focused.
798 * @private
800 createDisplayRectangle_: function(display, focused) {
801 var div = document.createElement('div');
802 display.div = div;
803 div.className = 'displays-display';
804 if (focused)
805 div.classList.add('displays-focused');
807 // div needs to be added to the DOM tree first, otherwise offsetHeight for
808 // nameContainer below cannot be computed.
809 this.displaysView_.appendChild(div);
811 var nameContainer = document.createElement('div');
812 nameContainer.textContent = display.name;
813 div.appendChild(nameContainer);
814 div.style.width = Math.floor(display.width * this.visualScale_) + 'px';
815 var newHeight = Math.floor(display.height * this.visualScale_);
816 div.style.height = newHeight + 'px';
817 nameContainer.style.marginTop =
818 (newHeight - nameContainer.offsetHeight) / 2 + 'px';
820 div.onmousedown = this.onMouseDown_.bind(this);
821 div.ontouchstart = this.onTouchStart_.bind(this);
822 return div;
826 * Layouts the display rectangles according to the current layout_.
827 * @private
829 layoutDisplays_: function() {
830 var maxWidth = 0;
831 var maxHeight = 0;
832 var boundingBox = {left: 0, right: 0, top: 0, bottom: 0};
833 this.primaryDisplay_ = null;
834 this.secondaryDisplay_ = null;
835 var focusedDisplay = null;
836 for (var i = 0; i < this.displays_.length; i++) {
837 var display = this.displays_[i];
838 if (display.isPrimary)
839 this.primaryDisplay_ = display;
840 else
841 this.secondaryDisplay_ = display;
842 if (i == this.focusedIndex_)
843 focusedDisplay = display;
845 boundingBox.left = Math.min(boundingBox.left, display.x);
846 boundingBox.right = Math.max(
847 boundingBox.right, display.x + display.width);
848 boundingBox.top = Math.min(boundingBox.top, display.y);
849 boundingBox.bottom = Math.max(
850 boundingBox.bottom, display.y + display.height);
851 maxWidth = Math.max(maxWidth, display.width);
852 maxHeight = Math.max(maxHeight, display.height);
854 if (!this.primaryDisplay_)
855 return;
857 // Make the margin around the bounding box.
858 var areaWidth = boundingBox.right - boundingBox.left + maxWidth;
859 var areaHeight = boundingBox.bottom - boundingBox.top + maxHeight;
861 // Calculates the scale by the width since horizontal size is more strict.
862 // TODO(mukai): Adds the check of vertical size in case.
863 this.visualScale_ = Math.min(
864 VISUAL_SCALE, this.displaysView_.offsetWidth / areaWidth);
866 // Prepare enough area for thisplays_view by adding the maximum height.
867 this.displaysView_.style.height =
868 Math.ceil(areaHeight * this.visualScale_) + 'px';
870 // Centering the bounding box of the display rectangles.
871 var offset = {
872 x: Math.floor(this.displaysView_.offsetWidth / 2 -
873 (boundingBox.right + boundingBox.left) * this.visualScale_ / 2),
874 y: Math.floor(this.displaysView_.offsetHeight / 2 -
875 (boundingBox.bottom + boundingBox.top) * this.visualScale_ / 2)
878 // Layouting the display rectangles. First layout the primaryDisplay and
879 // then layout the secondary which is attaching to the primary.
880 var primaryDiv = this.createDisplayRectangle_(
881 this.primaryDisplay_, this.primaryDisplay_ == focusedDisplay);
882 primaryDiv.style.left =
883 Math.floor(this.primaryDisplay_.x * this.visualScale_) +
884 offset.x + 'px';
885 primaryDiv.style.top =
886 Math.floor(this.primaryDisplay_.y * this.visualScale_) +
887 offset.y + 'px';
888 this.primaryDisplay_.originalPosition = {
889 x: primaryDiv.offsetLeft, y: primaryDiv.offsetTop};
891 if (this.secondaryDisplay_) {
892 var secondaryDiv = this.createDisplayRectangle_(
893 this.secondaryDisplay_, this.secondaryDisplay_ == focusedDisplay);
894 // Don't trust the secondary display's x or y, because it may cause a
895 // 1px gap due to rounding, which will create a fake update on end
896 // dragging. See crbug.com/386401
897 switch (this.layout_) {
898 case options.SecondaryDisplayLayout.TOP:
899 secondaryDiv.style.left =
900 Math.floor(this.secondaryDisplay_.x * this.visualScale_) +
901 offset.x + 'px';
902 secondaryDiv.style.top =
903 primaryDiv.offsetTop - secondaryDiv.offsetHeight + 'px';
904 break;
905 case options.SecondaryDisplayLayout.RIGHT:
906 secondaryDiv.style.left =
907 primaryDiv.offsetLeft + primaryDiv.offsetWidth + 'px';
908 secondaryDiv.style.top =
909 Math.floor(this.secondaryDisplay_.y * this.visualScale_) +
910 offset.y + 'px';
911 break;
912 case options.SecondaryDisplayLayout.BOTTOM:
913 secondaryDiv.style.left =
914 Math.floor(this.secondaryDisplay_.x * this.visualScale_) +
915 offset.x + 'px';
916 secondaryDiv.style.top =
917 primaryDiv.offsetTop + primaryDiv.offsetHeight + 'px';
918 break;
919 case options.SecondaryDisplayLayout.LEFT:
920 secondaryDiv.style.left =
921 primaryDiv.offsetLeft - secondaryDiv.offsetWidth + 'px';
922 secondaryDiv.style.top =
923 Math.floor(this.secondaryDisplay_.y * this.visualScale_) +
924 offset.y + 'px';
925 break;
927 this.secondaryDisplay_.originalPosition = {
928 x: secondaryDiv.offsetLeft, y: secondaryDiv.offsetTop};
933 * Called when the display arrangement has changed.
934 * @param {boolean} mirroring Whether current mode is mirroring or not.
935 * @param {Array<options.DisplayInfo>} displays The list of the display
936 * information.
937 * @param {options.SecondaryDisplayLayout} layout The layout strategy.
938 * @param {number} offset The offset of the secondary display.
939 * @private
941 onDisplayChanged_: function(mirroring, displays, layout, offset) {
942 if (!this.visible)
943 return;
945 var hasExternal = false;
946 for (var i = 0; i < displays.length; i++) {
947 if (!displays[i].isInternal) {
948 hasExternal = true;
949 break;
953 this.layout_ = layout;
955 $('display-options-toggle-mirroring').textContent =
956 loadTimeData.getString(
957 mirroring ? 'stopMirroring' : 'startMirroring');
959 // Focus to the first display next to the primary one when |displays| list
960 // is updated.
961 if (mirroring) {
962 this.focusedIndex_ = null;
963 } else if (this.mirroring_ != mirroring ||
964 this.displays_.length != displays.length) {
965 this.focusedIndex_ = 0;
968 this.mirroring_ = mirroring;
969 this.displays_ = displays;
971 this.resetDisplaysView_();
972 if (this.mirroring_)
973 this.layoutMirroringDisplays_();
974 else
975 this.layoutDisplays_();
977 this.updateSelectedDisplayDescription_();
981 DisplayOptions.setDisplayInfo = function(
982 mirroring, displays, layout, offset) {
983 DisplayOptions.getInstance().onDisplayChanged_(
984 mirroring, displays, layout, offset);
987 // Export
988 return {
989 DisplayOptions: DisplayOptions