Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
|
stevenjb
2016/01/26 01:31:26
Note: This is a new file diffing from display_opti
| |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 cr.exportPath('options'); | 5 cr.exportPath('options'); |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * Enumeration of display layout. These values must match the C++ values in | 8 * Enumeration of display layout. These values must match the C++ values in |
| 9 * ash::DisplayController. | 9 * ash::DisplayController. |
| 10 * @enum {number} | 10 * @enum {number} |
| 11 */ | 11 */ |
| 12 options.DisplayLayoutType = { | 12 options.DisplayLayoutType = { |
| 13 TOP: 0, | 13 TOP: 0, |
| 14 RIGHT: 1, | 14 RIGHT: 1, |
| 15 BOTTOM: 2, | 15 BOTTOM: 2, |
| 16 LEFT: 3 | 16 LEFT: 3 |
| 17 }; | 17 }; |
| 18 | 18 |
| 19 /** | 19 /** |
| 20 * Enumeration of multi display mode. These values must match the C++ values in | |
| 21 * ash::DisplayManager. | |
| 22 * @enum {number} | |
| 23 */ | |
| 24 options.MultiDisplayMode = { | |
| 25 EXTENDED: 0, | |
| 26 MIRRORING: 1, | |
| 27 UNIFIED: 2, | |
| 28 }; | |
| 29 | |
| 30 /** | |
| 31 * @typedef {{ | 20 * @typedef {{ |
| 32 * left: number, | 21 * left: number, |
| 33 * top: number, | 22 * top: number, |
| 34 * width: number, | 23 * width: number, |
| 35 * height: number | 24 * height: number |
| 36 * }} | 25 * }} |
| 37 */ | 26 */ |
| 38 options.DisplayBounds; | 27 options.DisplayBounds; |
| 39 | 28 |
| 40 /** | 29 /** |
| 41 * @typedef {{ | 30 * @typedef {{ |
| 42 * x: number, | 31 * x: number, |
| 43 * y: number | 32 * y: number |
| 44 * }} | 33 * }} |
| 45 */ | 34 */ |
| 46 options.DisplayPosition; | 35 options.DisplayPosition; |
| 47 | 36 |
| 48 /** | 37 /** |
| 49 * @typedef {{ | 38 * @typedef {{ |
| 50 * width: number, | |
| 51 * height: number, | |
| 52 * originalWidth: number, | |
| 53 * originalHeight: number, | |
| 54 * deviceScaleFactor: number, | |
| 55 * scale: number, | |
| 56 * refreshRate: number, | |
| 57 * isBest: boolean, | |
| 58 * selected: boolean | |
| 59 * }} | |
| 60 */ | |
| 61 options.DisplayMode; | |
| 62 | |
| 63 /** | |
| 64 * @typedef {{ | |
| 65 * profileId: number, | |
| 66 * name: string | |
| 67 * }} | |
| 68 */ | |
| 69 options.ColorProfile; | |
| 70 | |
| 71 /** | |
| 72 * @typedef {{ | |
| 73 * availableColorProfiles: !Array<!options.ColorProfile>, | |
| 74 * bounds: !options.DisplayBounds, | |
| 75 * colorProfileId: number, | |
| 76 * id: string, | |
| 77 * isInternal: boolean, | |
| 78 * isPrimary: boolean, | |
| 79 * resolutions: !Array<!options.DisplayMode>, | |
| 80 * name: string, | |
| 81 * rotation: number | |
| 82 * }} | |
| 83 */ | |
| 84 options.DisplayInfo; | |
| 85 | |
| 86 /** | |
| 87 * @typedef {{ | |
| 88 * bounds: !options.DisplayBounds, | 39 * bounds: !options.DisplayBounds, |
| 89 * div: ?HTMLElement, | 40 * div: ?HTMLElement, |
| 90 * id: string, | 41 * id: string, |
| 91 * isPrimary: boolean, | |
| 92 * layoutType: options.DisplayLayoutType, | 42 * layoutType: options.DisplayLayoutType, |
| 93 * name: string, | 43 * name: string, |
| 94 * originalPosition: !options.DisplayPosition | 44 * offset: number, |
| 45 * originalPosition: !options.DisplayPosition, | |
| 46 * parentId: string | |
| 95 * }} | 47 * }} |
| 96 */ | 48 */ |
| 97 options.DisplayLayout; | 49 options.DisplayLayout; |
| 98 | 50 |
| 99 cr.define('options', function() { | 51 cr.define('options', function() { |
| 100 var Page = cr.ui.pageManager.Page; | 52 'use strict'; |
| 101 var PageManager = cr.ui.pageManager.PageManager; | |
| 102 | |
| 103 // The scale ratio of the display rectangle to its original size. | |
| 104 /** @const */ var VISUAL_SCALE = 1 / 10; | |
| 105 | |
| 106 // The number of pixels to share the edges between displays. | |
| 107 /** @const */ var MIN_OFFSET_OVERLAP = 5; | |
| 108 | 53 |
| 109 /** | 54 /** |
| 110 * Gets the layout type of |point| relative to |rect|. | 55 * Gets the layout type of |point| relative to |rect|. |
| 111 * @param {!options.DisplayBounds} rect The base rectangle. | 56 * @param {!options.DisplayBounds} rect The base rectangle. |
| 112 * @param {!options.DisplayPosition} point The point to check the position. | 57 * @param {!options.DisplayPosition} point The point to check the position. |
| 113 * @return {options.DisplayLayoutType} The position of the calculated point. | 58 * @return {options.DisplayLayoutType} The position of the calculated point. |
| 114 */ | 59 */ |
| 115 function getPositionToRectangle(rect, point) { | 60 function getPositionToRectangle(rect, point) { |
| 116 // Separates the area into four (LEFT/RIGHT/TOP/BOTTOM) by the diagonals of | 61 // Separates the area into four (LEFT/RIGHT/TOP/BOTTOM) by the diagonals of |
| 117 // the rect, and decides which area the display should reside. | 62 // the rect, and decides which area the display should reside. |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 153 // Prefer the closer one if both edges are close enough. | 98 // Prefer the closer one if both edges are close enough. |
| 154 if (startDiff < SNAP_DISTANCE_PX && startDiff < endDiff) | 99 if (startDiff < SNAP_DISTANCE_PX && startDiff < endDiff) |
| 155 return basePoint; | 100 return basePoint; |
| 156 else if (endDiff < SNAP_DISTANCE_PX) | 101 else if (endDiff < SNAP_DISTANCE_PX) |
| 157 return basePoint + baseWidth - width; | 102 return basePoint + baseWidth - width; |
| 158 | 103 |
| 159 return point; | 104 return point; |
| 160 } | 105 } |
| 161 | 106 |
| 162 /** | 107 /** |
| 163 * Encapsulated handling of the 'Display' page. | 108 * @param {number} visualScale |
| 164 * @constructor | 109 * @constructor |
| 165 * @extends {cr.ui.pageManager.Page} | |
| 166 */ | 110 */ |
| 167 function DisplayOptions() { | 111 function DisplayLayoutManager(visualScale) { |
| 168 Page.call(this, 'display', | 112 this.visualScale_ = visualScale; |
| 169 loadTimeData.getString('displayOptionsPageTabTitle'), | |
| 170 'display-options-page'); | |
| 171 } | 113 } |
| 172 | 114 |
| 173 cr.addSingletonGetter(DisplayOptions); | 115 // Helper class for display layout management. Implements logic for laying |
| 174 | 116 // out two displays. |
| 175 DisplayOptions.prototype = { | 117 DisplayLayoutManager.prototype = { |
| 176 __proto__: Page.prototype, | |
| 177 | |
| 178 /** | 118 /** |
| 179 * Whether the current output status is mirroring displays or not. | 119 * An object containing DisplayLayout objects for each entry in |
| 180 * @type {boolean} | 120 * |displays_|. |
| 181 * @private | |
| 182 */ | |
| 183 mirroring_: false, | |
| 184 | |
| 185 /** | |
| 186 * Whether the unified desktop is enable or not. | |
| 187 * @type {boolean} | |
| 188 * @private | |
| 189 */ | |
| 190 unifiedDesktopEnabled_: false, | |
| 191 | |
| 192 /** | |
| 193 * Whether the unified desktop option should be present. | |
| 194 * @type {boolean} | |
| 195 * @private | |
| 196 */ | |
| 197 showUnifiedDesktopOption_: false, | |
| 198 | |
| 199 /** | |
| 200 * The array of current output displays. It also contains the display | |
| 201 * rectangles currently rendered on screen. | |
| 202 * @type {!Array<!options.DisplayInfo>} | |
| 203 * @private | |
| 204 */ | |
| 205 displays_: [], | |
| 206 | |
| 207 /** | |
| 208 * An object containing DisplayLayout objects for each entry in |displays_|. | |
| 209 * @type {!Object<!options.DisplayLayout>} | 121 * @type {!Object<!options.DisplayLayout>} |
| 210 * @private | 122 * @private |
| 211 */ | 123 */ |
| 212 displayLayoutMap_: {}, | 124 displayLayoutMap_: {}, |
| 213 | 125 |
| 214 /** | 126 /** |
| 215 * The id of the currently focused display, or empty for none. | |
| 216 * @type {string} | |
| 217 * @private | |
| 218 */ | |
| 219 focusedId_: '', | |
| 220 | |
| 221 /** | |
| 222 * The primary display id. | |
| 223 * @type {string} | |
| 224 * @private | |
| 225 */ | |
| 226 primaryDisplayId_: '', | |
| 227 | |
| 228 /** | |
| 229 * The secondary display id. | |
| 230 * @type {string} | |
| 231 * @private | |
| 232 */ | |
| 233 secondaryDisplayId_: '', | |
| 234 | |
| 235 /** | |
| 236 * Drag info. | |
| 237 * @type {?{displayId: string, | |
| 238 * originalLocation: !options.DisplayPosition, | |
| 239 * eventLocation: !options.DisplayPosition}} | |
| 240 * @private | |
| 241 */ | |
| 242 dragInfo_: null, | |
| 243 | |
| 244 /** | |
| 245 * The container div element which contains all of the display rectangles. | |
| 246 * @type {?Element} | |
| 247 * @private | |
| 248 */ | |
| 249 displaysView_: null, | |
| 250 | |
| 251 /** | |
| 252 * The scale factor of the actual display size to the drawn display | 127 * The scale factor of the actual display size to the drawn display |
| 253 * rectangle size. | 128 * rectangle size. Set to the correct value for the UI in the constructor. |
| 254 * @type {number} | 129 * @type {number} |
| 255 * @private | 130 * @private |
| 256 */ | 131 */ |
| 257 visualScale_: VISUAL_SCALE, | 132 visualScale_: 1, |
| 258 | 133 |
| 259 /** | 134 /** |
| 260 * The location where the last touch event happened. This is used to | 135 * Adds a display to the layout map. |
| 261 * prevent unnecessary dragging events happen. Set to null unless it's | 136 * @param {options.DisplayLayout} displayLayout |
| 262 * during touch events. | |
| 263 * @type {?options.DisplayPosition} | |
| 264 * @private | |
| 265 */ | 137 */ |
| 266 lastTouchLocation_: null, | 138 addDisplayLayout: function(displayLayout) { |
| 267 | 139 this.displayLayoutMap_[displayLayout.id] = displayLayout; |
| 268 /** | |
| 269 * Whether the display settings can be shown. | |
| 270 * @type {boolean} | |
| 271 * @private | |
| 272 */ | |
| 273 enabled_: true, | |
| 274 | |
| 275 /** @override */ | |
| 276 initializePage: function() { | |
| 277 Page.prototype.initializePage.call(this); | |
| 278 | |
| 279 $('display-options-select-mirroring').onchange = (function() { | |
| 280 this.mirroring_ = | |
| 281 $('display-options-select-mirroring').value == 'mirroring'; | |
| 282 chrome.send('setMirroring', [this.mirroring_]); | |
| 283 }).bind(this); | |
| 284 | |
| 285 var container = $('display-options-displays-view-host'); | |
| 286 container.onmousemove = this.onMouseMove_.bind(this); | |
| 287 window.addEventListener('mouseup', this.endDragging_.bind(this), true); | |
| 288 container.ontouchmove = this.onTouchMove_.bind(this); | |
| 289 container.ontouchend = this.endDragging_.bind(this); | |
| 290 | |
| 291 $('display-options-set-primary').onclick = (function() { | |
| 292 chrome.send('setPrimary', [this.focusedId_]); | |
| 293 }).bind(this); | |
| 294 $('display-options-resolution-selection').onchange = (function(ev) { | |
| 295 var display = this.getDisplayInfoFromId(this.focusedId_); | |
| 296 var resolution = display.resolutions[ev.target.value]; | |
| 297 chrome.send('setDisplayMode', [this.focusedId_, resolution]); | |
| 298 }).bind(this); | |
| 299 $('display-options-orientation-selection').onchange = (function(ev) { | |
| 300 var rotation = parseInt(ev.target.value, 10); | |
| 301 chrome.send('setRotation', [this.focusedId_, rotation]); | |
| 302 }).bind(this); | |
| 303 $('display-options-color-profile-selection').onchange = (function(ev) { | |
| 304 chrome.send('setColorProfile', [this.focusedId_, ev.target.value]); | |
| 305 }).bind(this); | |
| 306 $('selected-display-start-calibrating-overscan').onclick = (function() { | |
| 307 // Passes the target display ID. Do not specify it through URL hash, | |
| 308 // we do not care back/forward. | |
| 309 var displayOverscan = options.DisplayOverscan.getInstance(); | |
| 310 displayOverscan.setDisplayId(this.focusedId_); | |
| 311 PageManager.showPageByName('displayOverscan'); | |
| 312 chrome.send('coreOptionsUserMetricsAction', | |
| 313 ['Options_DisplaySetOverscan']); | |
| 314 }).bind(this); | |
| 315 | |
| 316 $('display-options-done').onclick = function() { | |
| 317 PageManager.closeOverlay(); | |
| 318 }; | |
| 319 | |
| 320 $('display-options-toggle-unified-desktop').onclick = (function() { | |
| 321 this.unifiedDesktopEnabled_ = !this.unifiedDesktopEnabled_; | |
| 322 chrome.send('setUnifiedDesktopEnabled', | |
| 323 [this.unifiedDesktopEnabled_]); | |
| 324 }).bind(this); | |
| 325 }, | |
| 326 | |
| 327 /** @override */ | |
| 328 didShowPage: function() { | |
| 329 var optionTitles = document.getElementsByClassName( | |
| 330 'selected-display-option-title'); | |
| 331 var maxSize = 0; | |
| 332 for (var i = 0; i < optionTitles.length; i++) | |
| 333 maxSize = Math.max(maxSize, optionTitles[i].clientWidth); | |
| 334 for (var i = 0; i < optionTitles.length; i++) | |
| 335 optionTitles[i].style.width = maxSize + 'px'; | |
| 336 chrome.send('getDisplayInfo'); | |
| 337 }, | |
| 338 | |
| 339 /** @override */ | |
| 340 canShowPage: function() { return this.enabled_; }, | |
| 341 | |
| 342 /** | |
| 343 * Enables or disables the page. When disabled, the page will not be able to | |
| 344 * open, and will close if currently opened. | |
| 345 * @param {boolean} enabled Whether the page should be enabled. | |
| 346 * @param {boolean} showUnifiedDesktop Whether the unified desktop option | |
| 347 * should be present. | |
| 348 */ | |
| 349 setEnabled: function(enabled, showUnifiedDesktop) { | |
| 350 if (this.enabled_ == enabled && | |
| 351 this.showUnifiedDesktopOption_ == showUnifiedDesktop) { | |
| 352 return; | |
| 353 } | |
| 354 this.enabled_ = enabled; | |
| 355 this.showUnifiedDesktopOption_ = showUnifiedDesktop; | |
| 356 if (!enabled && this.visible) | |
| 357 PageManager.closeOverlay(); | |
| 358 }, | 140 }, |
| 359 | 141 |
| 360 /** | 142 /** |
| 361 * Mouse move handler for dragging display rectangle. | 143 * Returns the layout type for |id|. |
| 362 * @param {Event} e The mouse move event. | 144 * @param {string} id |
| 363 * @private | 145 * @return {options.DisplayLayout} |
| 364 */ | 146 */ |
| 365 onMouseMove_: function(e) { | 147 getDisplayLayout: function(id) { return this.displayLayoutMap_[id]; }, |
| 366 return this.processDragging_(e, {x: e.pageX, y: e.pageY}); | 148 |
| 149 /** | |
| 150 * Creates a div for each entry in displayLayoutMap_. | |
| 151 * @param {!Element} parentElement The parent element to contain the div. | |
| 152 * @param {!options.DisplayPosition} offset The offset to the center of | |
| 153 * the display area. | |
| 154 */ | |
| 155 createDisplayLayoutDivs: function(parentElement, offset) { | |
| 156 for (var id in this.displayLayoutMap_) { | |
| 157 var layout = this.displayLayoutMap_[id]; | |
| 158 if (layout.div) | |
| 159 continue; // May have already been created by its child. | |
| 160 this.createDisplayLayoutDiv_(id, parentElement, offset); | |
| 161 } | |
| 367 }, | 162 }, |
| 368 | 163 |
| 369 /** | 164 /** |
| 370 * Touch move handler for dragging display rectangle. | 165 * Update the location of display |id| to |newPosition|. |
| 371 * @param {Event} e The touch move event. | 166 * @param {string} id |
| 167 * @param {options.DisplayPosition} newPosition | |
| 372 * @private | 168 * @private |
| 373 */ | 169 */ |
| 374 onTouchMove_: function(e) { | 170 updatePosition: function(id, newPosition) { |
| 375 if (e.touches.length != 1) | 171 var displayLayout = this.displayLayoutMap_[id]; |
| 376 return true; | 172 var div = displayLayout.div; |
| 377 | 173 var baseLayout = this.getBaseLayout_(displayLayout); |
| 378 var touchLocation = {x: e.touches[0].pageX, y: e.touches[0].pageY}; | |
| 379 // Touch move events happen even if the touch location doesn't change, but | |
| 380 // it doesn't need to process the dragging. Since sometimes the touch | |
| 381 // position changes slightly even though the user doesn't think to move | |
| 382 // the finger, very small move is just ignored. | |
| 383 /** @const */ var IGNORABLE_TOUCH_MOVE_PX = 1; | |
| 384 var xDiff = Math.abs(touchLocation.x - this.lastTouchLocation_.x); | |
| 385 var yDiff = Math.abs(touchLocation.y - this.lastTouchLocation_.y); | |
| 386 if (xDiff <= IGNORABLE_TOUCH_MOVE_PX && | |
| 387 yDiff <= IGNORABLE_TOUCH_MOVE_PX) { | |
| 388 return true; | |
| 389 } | |
| 390 | |
| 391 this.lastTouchLocation_ = touchLocation; | |
| 392 return this.processDragging_(e, touchLocation); | |
| 393 }, | |
| 394 | |
| 395 /** | |
| 396 * Mouse down handler for dragging display rectangle. | |
| 397 * @param {Event} e The mouse down event. | |
| 398 * @private | |
| 399 */ | |
| 400 onMouseDown_: function(e) { | |
| 401 if (this.mirroring_) | |
| 402 return true; | |
| 403 | |
| 404 if (e.button != 0) | |
| 405 return true; | |
| 406 | |
| 407 e.preventDefault(); | |
| 408 var target = assertInstanceof(e.target, HTMLElement); | |
| 409 return this.startDragging_(target, {x: e.pageX, y: e.pageY}); | |
| 410 }, | |
| 411 | |
| 412 /** | |
| 413 * Touch start handler for dragging display rectangle. | |
| 414 * @param {Event} e The touch start event. | |
| 415 * @private | |
| 416 */ | |
| 417 onTouchStart_: function(e) { | |
| 418 if (this.mirroring_) | |
| 419 return true; | |
| 420 | |
| 421 if (e.touches.length != 1) | |
| 422 return false; | |
| 423 | |
| 424 e.preventDefault(); | |
| 425 var touch = e.touches[0]; | |
| 426 this.lastTouchLocation_ = {x: touch.pageX, y: touch.pageY}; | |
| 427 var target = assertInstanceof(e.target, HTMLElement); | |
| 428 return this.startDragging_(target, this.lastTouchLocation_); | |
| 429 }, | |
| 430 | |
| 431 /** | |
| 432 * @param {string} id | |
| 433 * @return {options.DisplayInfo} | |
| 434 */ | |
| 435 getDisplayInfoFromId(id) { | |
| 436 return this.displays_.find(function(display) { | |
| 437 return display.id == id; | |
| 438 }); | |
| 439 }, | |
| 440 | |
| 441 /** | |
| 442 * Collects the current data and sends it to Chrome. | |
| 443 * @private | |
| 444 */ | |
| 445 sendDragResult_: function() { | |
| 446 // Offset is calculated from top or left edge. | |
| 447 var primary = this.displayLayoutMap_[this.primaryDisplayId_]; | |
| 448 var secondary = this.displayLayoutMap_[this.secondaryDisplayId_]; | |
| 449 var layoutType = secondary.layoutType; | |
| 450 var offset; | |
| 451 if (layoutType == options.DisplayLayoutType.LEFT || | |
| 452 layoutType == options.DisplayLayoutType.RIGHT) { | |
| 453 offset = secondary.div.offsetTop - primary.div.offsetTop; | |
| 454 } else { | |
| 455 offset = secondary.div.offsetLeft - primary.div.offsetLeft; | |
| 456 } | |
| 457 offset = Math.floor(offset / this.visualScale_); | |
| 458 chrome.send('setDisplayLayout', [secondary.id, layoutType, offset]); | |
| 459 }, | |
| 460 | |
| 461 /** | |
| 462 * Processes the actual dragging of display rectangle. | |
| 463 * @param {Event} e The event which triggers this drag. | |
| 464 * @param {options.DisplayPosition} eventLocation The location where the | |
| 465 * event happens. | |
| 466 * @private | |
| 467 */ | |
| 468 processDragging_: function(e, eventLocation) { | |
| 469 if (!this.dragInfo_) | |
| 470 return true; | |
| 471 | |
| 472 e.preventDefault(); | |
| 473 | |
| 474 // Note that current code of moving display-rectangles doesn't work | |
| 475 // if there are >=3 displays. This is our assumption for M21. | |
| 476 // TODO(mukai): Fix the code to allow >=3 displays. | |
| 477 var dragInfo = this.dragInfo_; | |
| 478 /** @type {options.DisplayPosition} */ var newPosition = { | |
| 479 x: dragInfo.originalLocation.x + | |
| 480 (eventLocation.x - dragInfo.eventLocation.x), | |
| 481 y: dragInfo.originalLocation.y + | |
| 482 (eventLocation.y - dragInfo.eventLocation.y) | |
| 483 }; | |
| 484 | |
| 485 var dragLayout = this.displayLayoutMap_[dragInfo.displayId]; | |
| 486 var draggingDiv = dragLayout.div; | |
| 487 | |
| 488 var baseDisplayId = dragLayout.isPrimary ? this.secondaryDisplayId_ : | |
| 489 this.primaryDisplayId_; | |
| 490 var baseLayout = this.displayLayoutMap_[baseDisplayId]; | |
| 491 var baseDiv = baseLayout.div; | 174 var baseDiv = baseLayout.div; |
| 492 | 175 |
| 493 newPosition.x = snapToEdge( | 176 newPosition.x = snapToEdge( |
| 494 newPosition.x, draggingDiv.offsetWidth, baseDiv.offsetLeft, | 177 newPosition.x, div.offsetWidth, baseDiv.offsetLeft, |
| 495 baseDiv.offsetWidth); | 178 baseDiv.offsetWidth); |
| 496 newPosition.y = snapToEdge( | 179 newPosition.y = snapToEdge( |
| 497 newPosition.y, draggingDiv.offsetHeight, baseDiv.offsetTop, | 180 newPosition.y, div.offsetHeight, baseDiv.offsetTop, |
| 498 baseDiv.offsetHeight); | 181 baseDiv.offsetHeight); |
| 499 | 182 |
| 500 /** @type {!options.DisplayPosition} */ var newCenter = { | 183 /** @type {!options.DisplayPosition} */ var newCenter = { |
| 501 x: newPosition.x + draggingDiv.offsetWidth / 2, | 184 x: newPosition.x + div.offsetWidth / 2, |
| 502 y: newPosition.y + draggingDiv.offsetHeight / 2 | 185 y: newPosition.y + div.offsetHeight / 2 |
| 503 }; | 186 }; |
| 504 | 187 |
| 505 /** @type {!options.DisplayBounds} */ var baseBounds = { | 188 /** @type {!options.DisplayBounds} */ var baseBounds = { |
| 506 left: baseDiv.offsetLeft, | 189 left: baseDiv.offsetLeft, |
| 507 top: baseDiv.offsetTop, | 190 top: baseDiv.offsetTop, |
| 508 width: baseDiv.offsetWidth, | 191 width: baseDiv.offsetWidth, |
| 509 height: baseDiv.offsetHeight | 192 height: baseDiv.offsetHeight |
| 510 }; | 193 }; |
| 511 | 194 |
| 512 var isPrimary = dragLayout.isPrimary; | 195 // This implementation considers only the case of two displays, i.e |
| 513 // layoutType is always stored in the secondary layout. | 196 // a single parent with one child. |
| 197 var isPrimary = displayLayout.parentId == ''; | |
| 198 | |
| 199 // layoutType is always stored in the child layout. | |
| 514 var layoutType = | 200 var layoutType = |
| 515 isPrimary ? baseLayout.layoutType : dragLayout.layoutType; | 201 isPrimary ? baseLayout.layoutType : displayLayout.layoutType; |
| 516 | 202 |
| 517 switch (getPositionToRectangle(baseBounds, newCenter)) { | 203 switch (getPositionToRectangle(baseBounds, newCenter)) { |
| 518 case options.DisplayLayoutType.RIGHT: | 204 case options.DisplayLayoutType.RIGHT: |
| 519 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : | 205 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : |
| 520 options.DisplayLayoutType.RIGHT; | 206 options.DisplayLayoutType.RIGHT; |
| 521 break; | 207 break; |
| 522 case options.DisplayLayoutType.LEFT: | 208 case options.DisplayLayoutType.LEFT: |
| 523 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : | 209 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : |
| 524 options.DisplayLayoutType.LEFT; | 210 options.DisplayLayoutType.LEFT; |
| 525 break; | 211 break; |
| 526 case options.DisplayLayoutType.TOP: | 212 case options.DisplayLayoutType.TOP: |
| 527 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : | 213 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : |
| 528 options.DisplayLayoutType.TOP; | 214 options.DisplayLayoutType.TOP; |
| 529 break; | 215 break; |
| 530 case options.DisplayLayoutType.BOTTOM: | 216 case options.DisplayLayoutType.BOTTOM: |
| 531 layoutType = isPrimary ? options.DisplayLayoutType.TOP : | 217 layoutType = isPrimary ? options.DisplayLayoutType.TOP : |
| 532 options.DisplayLayoutType.BOTTOM; | 218 options.DisplayLayoutType.BOTTOM; |
| 533 break; | 219 break; |
| 534 } | 220 } |
| 535 | 221 |
| 536 if (layoutType == options.DisplayLayoutType.LEFT || | 222 if (layoutType == options.DisplayLayoutType.LEFT || |
| 537 layoutType == options.DisplayLayoutType.RIGHT) { | 223 layoutType == options.DisplayLayoutType.RIGHT) { |
| 538 if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight) | 224 if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight) |
| 539 layoutType = isPrimary ? options.DisplayLayoutType.TOP : | 225 layoutType = isPrimary ? options.DisplayLayoutType.TOP : |
| 540 options.DisplayLayoutType.BOTTOM; | 226 options.DisplayLayoutType.BOTTOM; |
| 541 else if (newPosition.y + draggingDiv.offsetHeight < baseDiv.offsetTop) | 227 else if (newPosition.y + div.offsetHeight < baseDiv.offsetTop) |
| 542 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : | 228 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : |
| 543 options.DisplayLayoutType.TOP; | 229 options.DisplayLayoutType.TOP; |
| 544 } else { | 230 } else { |
| 545 if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth) | 231 if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth) |
| 546 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : | 232 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : |
| 547 options.DisplayLayoutType.RIGHT; | 233 options.DisplayLayoutType.RIGHT; |
| 548 else if (newPosition.x + draggingDiv.offsetWidth < baseDiv.offsetLeft) | 234 else if (newPosition.x + div.offsetWidth < baseDiv.offsetLeft) |
| 549 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : | 235 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : |
| 550 options.DisplayLayoutType.LEFT; | 236 options.DisplayLayoutType.LEFT; |
| 551 } | 237 } |
| 552 | 238 |
| 553 var layoutToBase; | 239 var layoutToBase; |
| 554 if (!isPrimary) { | 240 if (!isPrimary) { |
| 555 dragLayout.layoutType = layoutType; | 241 displayLayout.layoutType = layoutType; |
| 556 layoutToBase = layoutType; | 242 layoutToBase = layoutType; |
| 557 } else { | 243 } else { |
| 558 baseLayout.layoutType = layoutType; | 244 baseLayout.layoutType = layoutType; |
| 559 switch (layoutType) { | 245 switch (layoutType) { |
| 560 case options.DisplayLayoutType.RIGHT: | 246 case options.DisplayLayoutType.RIGHT: |
| 561 layoutToBase = options.DisplayLayoutType.LEFT; | 247 layoutToBase = options.DisplayLayoutType.LEFT; |
| 562 break; | 248 break; |
| 563 case options.DisplayLayoutType.LEFT: | 249 case options.DisplayLayoutType.LEFT: |
| 564 layoutToBase = options.DisplayLayoutType.RIGHT; | 250 layoutToBase = options.DisplayLayoutType.RIGHT; |
| 565 break; | 251 break; |
| 566 case options.DisplayLayoutType.TOP: | 252 case options.DisplayLayoutType.TOP: |
| 567 layoutToBase = options.DisplayLayoutType.BOTTOM; | 253 layoutToBase = options.DisplayLayoutType.BOTTOM; |
| 568 break; | 254 break; |
| 569 case options.DisplayLayoutType.BOTTOM: | 255 case options.DisplayLayoutType.BOTTOM: |
| 570 layoutToBase = options.DisplayLayoutType.TOP; | 256 layoutToBase = options.DisplayLayoutType.TOP; |
| 571 break; | 257 break; |
| 572 } | 258 } |
| 573 } | 259 } |
| 574 | 260 |
| 575 switch (layoutToBase) { | 261 switch (layoutToBase) { |
| 576 case options.DisplayLayoutType.RIGHT: | 262 case options.DisplayLayoutType.RIGHT: |
| 577 draggingDiv.style.left = | 263 div.style.left = baseDiv.offsetLeft + baseDiv.offsetWidth + 'px'; |
| 578 baseDiv.offsetLeft + baseDiv.offsetWidth + 'px'; | 264 div.style.top = newPosition.y + 'px'; |
| 579 draggingDiv.style.top = newPosition.y + 'px'; | |
| 580 break; | 265 break; |
| 581 case options.DisplayLayoutType.LEFT: | 266 case options.DisplayLayoutType.LEFT: |
| 582 draggingDiv.style.left = | 267 div.style.left = baseDiv.offsetLeft - div.offsetWidth + 'px'; |
| 583 baseDiv.offsetLeft - draggingDiv.offsetWidth + 'px'; | 268 div.style.top = newPosition.y + 'px'; |
| 584 draggingDiv.style.top = newPosition.y + 'px'; | |
| 585 break; | 269 break; |
| 586 case options.DisplayLayoutType.TOP: | 270 case options.DisplayLayoutType.TOP: |
| 587 draggingDiv.style.top = | 271 div.style.top = baseDiv.offsetTop - div.offsetHeight + 'px'; |
| 588 baseDiv.offsetTop - draggingDiv.offsetHeight + 'px'; | 272 div.style.left = newPosition.x + 'px'; |
| 589 draggingDiv.style.left = newPosition.x + 'px'; | |
| 590 break; | 273 break; |
| 591 case options.DisplayLayoutType.BOTTOM: | 274 case options.DisplayLayoutType.BOTTOM: |
| 592 draggingDiv.style.top = | 275 div.style.top = baseDiv.offsetTop + baseDiv.offsetHeight + 'px'; |
| 593 baseDiv.offsetTop + baseDiv.offsetHeight + 'px'; | 276 div.style.left = newPosition.x + 'px'; |
| 594 draggingDiv.style.left = newPosition.x + 'px'; | |
| 595 break; | 277 break; |
| 596 } | 278 } |
| 597 | |
| 598 return false; | |
| 599 }, | 279 }, |
| 600 | 280 |
| 601 /** | 281 /** |
| 602 * Start dragging of a display rectangle. | 282 * Finalize the location of display |id| after updates are complete |
| 603 * @param {!HTMLElement} target The event target. | 283 * (e.g. draging has ended). |
| 604 * @param {!options.DisplayPosition} eventLocation The event location. | 284 * @param {string} id |
| 605 * @private | 285 * @return {boolean} True if the final position differs from the original. |
| 606 */ | 286 */ |
| 607 startDragging_: function(target, eventLocation) { | 287 finalizePosition: function(id) { |
| 608 var oldFocusedId = this.focusedId_; | 288 // Make sure the dragging location is connected. |
| 609 var newFocusedId; | 289 var displayLayout = this.displayLayoutMap_[id]; |
| 610 var willUpdateDisplayDescription = false; | 290 var div = displayLayout.div; |
| 611 for (var i = 0; i < this.displays_.length; i++) { | 291 var baseLayout = this.getBaseLayout_(displayLayout); |
| 612 var displayLayout = this.displayLayoutMap_[this.displays_[i].id]; | 292 var baseDiv = baseLayout.div; |
| 613 if (displayLayout.div == target || | |
| 614 (target.offsetParent && target.offsetParent == displayLayout.div)) { | |
| 615 newFocusedId = displayLayout.id; | |
| 616 break; | |
| 617 } | |
| 618 } | |
| 619 if (!newFocusedId) | |
| 620 return false; | |
| 621 | 293 |
| 622 this.focusedId_ = newFocusedId; | 294 var isPrimary = displayLayout.parentId == ''; |
| 623 willUpdateDisplayDescription = newFocusedId != oldFocusedId; | 295 var layoutType = |
| 296 isPrimary ? baseLayout.layoutType : displayLayout.layoutType; | |
| 624 | 297 |
| 625 for (var i = 0; i < this.displays_.length; i++) { | 298 // The number of pixels to share the edges between displays. |
| 626 var displayLayout = this.displayLayoutMap_[this.displays_[i].id]; | 299 /** @const */ var MIN_OFFSET_OVERLAP = 5; |
| 627 displayLayout.div.className = 'displays-display'; | |
| 628 if (displayLayout.id != this.focusedId_) | |
| 629 continue; | |
| 630 | |
| 631 displayLayout.div.classList.add('displays-focused'); | |
| 632 if (this.displays_.length > 1) { | |
| 633 this.dragInfo_ = { | |
| 634 displayId: displayLayout.id, | |
| 635 originalLocation: { | |
| 636 x: displayLayout.div.offsetLeft, | |
| 637 y: displayLayout.div.offsetTop | |
| 638 }, | |
| 639 eventLocation: {x: eventLocation.x, y: eventLocation.y} | |
| 640 }; | |
| 641 } | |
| 642 } | |
| 643 | |
| 644 if (willUpdateDisplayDescription) | |
| 645 this.updateSelectedDisplayDescription_(); | |
| 646 return false; | |
| 647 }, | |
| 648 | |
| 649 /** | |
| 650 * finish the current dragging of displays. | |
| 651 * @param {Event} e The event which triggers this. | |
| 652 * @private | |
| 653 */ | |
| 654 endDragging_: function(e) { | |
| 655 this.lastTouchLocation_ = null; | |
| 656 if (!this.dragInfo_) | |
| 657 return false; | |
| 658 | |
| 659 // Make sure the dragging location is connected. | |
| 660 var dragLayout = this.displayLayoutMap_[this.dragInfo_.displayId]; | |
| 661 var baseDisplayId = dragLayout.isPrimary ? this.secondaryDisplayId_ : | |
| 662 this.primaryDisplayId_; | |
| 663 | |
| 664 var baseLayout = this.displayLayoutMap_[baseDisplayId]; | |
| 665 var baseDiv = baseLayout.div; | |
| 666 var draggingDiv = dragLayout.div; | |
| 667 | |
| 668 // layoutType is always stored in the secondary layout. | |
| 669 var layoutType = | |
| 670 dragLayout.isPrimary ? baseLayout.layoutType : dragLayout.layoutType; | |
| 671 | 300 |
| 672 if (layoutType == options.DisplayLayoutType.LEFT || | 301 if (layoutType == options.DisplayLayoutType.LEFT || |
| 673 layoutType == options.DisplayLayoutType.RIGHT) { | 302 layoutType == options.DisplayLayoutType.RIGHT) { |
| 674 var top = Math.max( | 303 var top = Math.max( |
| 675 draggingDiv.offsetTop, | 304 div.offsetTop, |
| 676 baseDiv.offsetTop - draggingDiv.offsetHeight + MIN_OFFSET_OVERLAP); | 305 baseDiv.offsetTop - div.offsetHeight + MIN_OFFSET_OVERLAP); |
| 677 top = Math.min( | 306 top = Math.min( |
| 678 top, baseDiv.offsetTop + baseDiv.offsetHeight - MIN_OFFSET_OVERLAP); | 307 top, baseDiv.offsetTop + baseDiv.offsetHeight - MIN_OFFSET_OVERLAP); |
| 679 draggingDiv.style.top = top + 'px'; | 308 div.style.top = top + 'px'; |
| 680 } else { | 309 } else { |
| 681 var left = Math.max( | 310 var left = Math.max( |
| 682 draggingDiv.offsetLeft, | 311 div.offsetLeft, |
| 683 baseDiv.offsetLeft - draggingDiv.offsetWidth + MIN_OFFSET_OVERLAP); | 312 baseDiv.offsetLeft - div.offsetWidth + MIN_OFFSET_OVERLAP); |
| 684 left = Math.min( | 313 left = Math.min( |
| 685 left, | 314 left, |
| 686 baseDiv.offsetLeft + baseDiv.offsetWidth - MIN_OFFSET_OVERLAP); | 315 baseDiv.offsetLeft + baseDiv.offsetWidth - MIN_OFFSET_OVERLAP); |
| 687 draggingDiv.style.left = left + 'px'; | 316 div.style.left = left + 'px'; |
| 688 } | |
| 689 if (dragLayout.originalPosition.x != draggingDiv.offsetLeft || | |
| 690 dragLayout.originalPosition.y != draggingDiv.offsetTop) { | |
| 691 this.sendDragResult_(); | |
| 692 } | 317 } |
| 693 | 318 |
| 694 this.dragInfo_ = null; | 319 // Calculate the offset of the child display. |
| 320 this.calculateOffset_(isPrimary ? baseLayout : displayLayout); | |
| 695 | 321 |
| 696 return false; | 322 return displayLayout.originalPosition.x != div.offsetLeft || |
| 323 displayLayout.originalPosition.y != div.offsetTop; | |
| 697 }, | 324 }, |
| 698 | 325 |
| 699 /** | 326 /** |
| 700 * Updates the description of selected display section for mirroring mode. | 327 * Creates a div element and assigns it to |displayLayout|. Returns the |
| 701 * @private | 328 * created div for additional decoration. |
| 329 * @param {string} id | |
| 330 * @param {!Element} parentElement The parent element to contain the div. | |
| 331 * @param {!options.DisplayPosition} offset The offset to the center of | |
| 332 * the display area. | |
| 702 */ | 333 */ |
| 703 updateSelectedDisplaySectionMirroring_: function() { | 334 createDisplayLayoutDiv_: function(id, parentElement, offset) { |
| 704 $('display-configuration-arrow').hidden = true; | 335 var displayLayout = this.displayLayoutMap_[id]; |
| 705 $('display-options-set-primary').disabled = true; | 336 var parentId = displayLayout.parentId; |
| 706 $('display-options-select-mirroring').disabled = false; | 337 if (parentId) { |
| 707 $('selected-display-start-calibrating-overscan').disabled = true; | 338 // Ensure the parent div is created first. |
| 708 var display = this.displays_[0]; | 339 var parentLayout = this.displayLayoutMap_[parentId]; |
| 709 var orientation = $('display-options-orientation-selection'); | 340 if (!parentLayout.div) |
| 710 orientation.disabled = false; | 341 this.createDisplayLayoutDiv_(parentId, parentElement, offset); |
| 711 var orientationOptions = orientation.getElementsByTagName('option'); | |
| 712 var orientationIndex = Math.floor(display.rotation / 90); | |
| 713 orientationOptions[orientationIndex].selected = true; | |
| 714 $('selected-display-name').textContent = | |
| 715 loadTimeData.getString('mirroringDisplay'); | |
| 716 var resolution = $('display-options-resolution-selection'); | |
| 717 var option = document.createElement('option'); | |
| 718 option.value = 'default'; | |
| 719 option.textContent = display.bounds.width + 'x' + display.bounds.height; | |
| 720 resolution.appendChild(option); | |
| 721 resolution.disabled = true; | |
| 722 }, | |
| 723 | |
| 724 /** | |
| 725 * Updates the description of selected display section when no display is | |
| 726 * selected. | |
| 727 * @private | |
| 728 */ | |
| 729 updateSelectedDisplaySectionNoSelected_: function() { | |
| 730 $('display-configuration-arrow').hidden = true; | |
| 731 $('display-options-set-primary').disabled = true; | |
| 732 $('display-options-select-mirroring').disabled = true; | |
| 733 $('selected-display-start-calibrating-overscan').disabled = true; | |
| 734 $('display-options-orientation-selection').disabled = true; | |
| 735 $('selected-display-name').textContent = ''; | |
| 736 var resolution = $('display-options-resolution-selection'); | |
| 737 resolution.appendChild(document.createElement('option')); | |
| 738 resolution.disabled = true; | |
| 739 }, | |
| 740 | |
| 741 /** | |
| 742 * Updates the description of selected display section for the selected | |
| 743 * display. | |
| 744 * @param {options.DisplayInfo} display The selected display object. | |
| 745 * @private | |
| 746 */ | |
| 747 updateSelectedDisplaySectionForDisplay_: function(display) { | |
| 748 var displayLayout = this.displayLayoutMap_[display.id]; | |
| 749 var arrow = $('display-configuration-arrow'); | |
| 750 arrow.hidden = false; | |
| 751 // Adding 1 px to the position to fit the border line and the border in | |
| 752 // arrow precisely. | |
| 753 arrow.style.top = $('display-configurations').offsetTop - | |
| 754 arrow.offsetHeight / 2 + 'px'; | |
| 755 arrow.style.left = displayLayout.div.offsetLeft + | |
| 756 displayLayout.div.offsetWidth / 2 - arrow.offsetWidth / 2 + 'px'; | |
| 757 | |
| 758 $('display-options-set-primary').disabled = display.isPrimary; | |
| 759 $('display-options-select-mirroring').disabled = | |
| 760 (this.displays_.length <= 1 && !this.unifiedDesktopEnabled_); | |
| 761 $('selected-display-start-calibrating-overscan').disabled = | |
| 762 display.isInternal; | |
| 763 | |
| 764 var orientation = $('display-options-orientation-selection'); | |
| 765 orientation.disabled = this.unifiedDesktopEnabled_; | |
| 766 | |
| 767 var orientationOptions = orientation.getElementsByTagName('option'); | |
| 768 var orientationIndex = Math.floor(display.rotation / 90); | |
| 769 orientationOptions[orientationIndex].selected = true; | |
| 770 | |
| 771 $('selected-display-name').textContent = display.name; | |
| 772 | |
| 773 var resolution = $('display-options-resolution-selection'); | |
| 774 if (display.resolutions.length <= 1) { | |
| 775 var option = document.createElement('option'); | |
| 776 option.value = 'default'; | |
| 777 option.textContent = display.bounds.width + 'x' + display.bounds.height; | |
| 778 option.selected = true; | |
| 779 resolution.appendChild(option); | |
| 780 resolution.disabled = true; | |
| 781 } else { | |
| 782 var previousOption; | |
| 783 for (var i = 0; i < display.resolutions.length; i++) { | |
| 784 var option = document.createElement('option'); | |
| 785 option.value = i; | |
| 786 option.textContent = display.resolutions[i].width + 'x' + | |
| 787 display.resolutions[i].height; | |
| 788 if (display.resolutions[i].isBest) { | |
| 789 option.textContent += ' ' + | |
| 790 loadTimeData.getString('annotateBest'); | |
| 791 } else if (display.resolutions[i].isNative) { | |
| 792 option.textContent += ' ' + | |
| 793 loadTimeData.getString('annotateNative'); | |
| 794 } | |
| 795 if (display.resolutions[i].deviceScaleFactor && previousOption && | |
| 796 previousOption.textContent == option.textContent) { | |
| 797 option.textContent += | |
| 798 ' (' + display.resolutions[i].deviceScaleFactor + 'x)'; | |
| 799 } | |
| 800 option.selected = display.resolutions[i].selected; | |
| 801 resolution.appendChild(option); | |
| 802 previousOption = option; | |
| 803 } | |
| 804 resolution.disabled = (display.resolutions.length <= 1); | |
| 805 } | 342 } |
| 806 | 343 |
| 807 if (display.availableColorProfiles.length <= 1) { | |
| 808 $('selected-display-color-profile-row').hidden = true; | |
| 809 } else { | |
| 810 $('selected-display-color-profile-row').hidden = false; | |
| 811 var profiles = $('display-options-color-profile-selection'); | |
| 812 profiles.innerHTML = ''; | |
| 813 for (var i = 0; i < display.availableColorProfiles.length; i++) { | |
| 814 var option = document.createElement('option'); | |
| 815 var colorProfile = display.availableColorProfiles[i]; | |
| 816 option.value = colorProfile.profileId; | |
| 817 option.textContent = colorProfile.name; | |
| 818 option.selected = (display.colorProfileId == colorProfile.profileId); | |
| 819 profiles.appendChild(option); | |
| 820 } | |
| 821 } | |
| 822 }, | |
| 823 | |
| 824 /** | |
| 825 * Updates the description of the selected display section. | |
| 826 * @private | |
| 827 */ | |
| 828 updateSelectedDisplayDescription_: function() { | |
| 829 var resolution = $('display-options-resolution-selection'); | |
| 830 resolution.textContent = ''; | |
| 831 var orientation = $('display-options-orientation-selection'); | |
| 832 var orientationOptions = orientation.getElementsByTagName('option'); | |
| 833 for (var i = 0; i < orientationOptions.length; i++) | |
| 834 orientationOptions[i].selected = false; | |
| 835 | |
| 836 if (this.mirroring_) { | |
| 837 this.updateSelectedDisplaySectionMirroring_(); | |
| 838 } else if (this.focusedId_ == '') { | |
| 839 this.updateSelectedDisplaySectionNoSelected_(); | |
| 840 } else { | |
| 841 this.updateSelectedDisplaySectionForDisplay_( | |
| 842 this.getDisplayInfoFromId(this.focusedId_)); | |
| 843 } | |
| 844 }, | |
| 845 | |
| 846 /** | |
| 847 * Clears the drawing area for display rectangles. | |
| 848 * @private | |
| 849 */ | |
| 850 resetDisplaysView_: function() { | |
| 851 var displaysViewHost = $('display-options-displays-view-host'); | |
| 852 displaysViewHost.removeChild(displaysViewHost.firstChild); | |
| 853 this.displaysView_ = document.createElement('div'); | |
| 854 this.displaysView_.id = 'display-options-displays-view'; | |
| 855 displaysViewHost.appendChild(this.displaysView_); | |
| 856 }, | |
| 857 | |
| 858 /** | |
| 859 * Lays out the display rectangles for mirroring. | |
| 860 * @private | |
| 861 */ | |
| 862 layoutMirroringDisplays_: function() { | |
| 863 // Offset pixels for secondary display rectangles. The offset includes the | |
| 864 // border width. | |
| 865 /** @const */ var MIRRORING_OFFSET_PIXELS = 3; | |
| 866 // Always show two displays because there must be two displays when | |
| 867 // the display_options is enabled. Don't rely on displays_.length because | |
| 868 // there is only one display from chrome's perspective in mirror mode. | |
| 869 /** @const */ var MIN_NUM_DISPLAYS = 2; | |
| 870 /** @const */ var MIRRORING_VERTICAL_MARGIN = 20; | |
| 871 | |
| 872 // The width/height should be same as the first display: | |
| 873 var width = Math.ceil(this.displays_[0].bounds.width * this.visualScale_); | |
| 874 var height = | |
| 875 Math.ceil(this.displays_[0].bounds.height * this.visualScale_); | |
| 876 | |
| 877 var numDisplays = Math.max(MIN_NUM_DISPLAYS, this.displays_.length); | |
| 878 | |
| 879 var totalWidth = width + numDisplays * MIRRORING_OFFSET_PIXELS; | |
| 880 var totalHeight = height + numDisplays * MIRRORING_OFFSET_PIXELS; | |
| 881 | |
| 882 this.displaysView_.style.height = totalHeight + 'px'; | |
| 883 | |
| 884 // The displays should be centered. | |
| 885 var offsetX = | |
| 886 $('display-options-displays-view').offsetWidth / 2 - totalWidth / 2; | |
| 887 | |
| 888 for (var i = 0; i < numDisplays; i++) { | |
| 889 var div = /** @type {HTMLElement} */ (document.createElement('div')); | |
| 890 div.className = 'displays-display'; | |
| 891 div.style.top = i * MIRRORING_OFFSET_PIXELS + 'px'; | |
| 892 div.style.left = i * MIRRORING_OFFSET_PIXELS + offsetX + 'px'; | |
| 893 div.style.width = width + 'px'; | |
| 894 div.style.height = height + 'px'; | |
| 895 div.style.zIndex = i; | |
| 896 // set 'display-mirrored' class for the background display rectangles. | |
| 897 if (i != numDisplays - 1) | |
| 898 div.classList.add('display-mirrored'); | |
| 899 this.displaysView_.appendChild(div); | |
| 900 } | |
| 901 }, | |
| 902 | |
| 903 /** | |
| 904 * Creates a DisplayLayout object representing the display. | |
| 905 * @param {!options.DisplayInfo} display | |
| 906 * @param {!options.DisplayLayoutType} layoutType | |
| 907 * @return {!options.DisplayLayout} | |
| 908 * @private | |
| 909 */ | |
| 910 createDisplayLayout_: function(display, layoutType) { | |
| 911 return { | |
| 912 bounds: display.bounds, | |
| 913 div: null, | |
| 914 id: display.id, | |
| 915 isPrimary: display.isPrimary, | |
| 916 layoutType: layoutType, | |
| 917 name: display.name, | |
| 918 originalPosition: {x: 0, y: 0} | |
| 919 }; | |
| 920 }, | |
| 921 | |
| 922 /** | |
| 923 * Creates a div element representing the specified display. | |
| 924 * @param {!options.DisplayLayout} displayLayout | |
| 925 * @param {options.DisplayLayoutType} layoutType The layout type for the | |
| 926 * secondary display. | |
| 927 * @param {!options.DisplayPosition} offset The offset to the center of the | |
| 928 * display area. | |
| 929 * @private | |
| 930 */ | |
| 931 createDisplayLayoutDiv_: function(displayLayout, layoutType, offset) { | |
| 932 var div = /** @type {!HTMLElement} */ (document.createElement('div')); | 344 var div = /** @type {!HTMLElement} */ (document.createElement('div')); |
| 933 div.className = 'displays-display'; | 345 div.className = 'displays-display'; |
| 934 div.classList.toggle( | |
| 935 'displays-focused', displayLayout.id == this.focusedId_); | |
| 936 | 346 |
| 937 // div needs to be added to the DOM tree first, otherwise offsetHeight for | 347 // div needs to be added to the DOM tree first, otherwise offsetHeight for |
| 938 // nameContainer below cannot be computed. | 348 // nameContainer below cannot be computed. |
| 939 this.displaysView_.appendChild(div); | 349 parentElement.appendChild(div); |
| 940 | 350 |
| 941 var nameContainer = document.createElement('div'); | 351 var nameContainer = document.createElement('div'); |
| 942 nameContainer.textContent = displayLayout.name; | 352 nameContainer.textContent = displayLayout.name; |
| 943 div.appendChild(nameContainer); | 353 div.appendChild(nameContainer); |
| 944 | 354 |
| 945 var bounds = displayLayout.bounds; | 355 var bounds = displayLayout.bounds; |
| 946 div.style.width = Math.floor(bounds.width * this.visualScale_) + 'px'; | 356 div.style.width = Math.floor(bounds.width * this.visualScale_) + 'px'; |
| 947 var newHeight = Math.floor(bounds.height * this.visualScale_); | 357 var newHeight = Math.floor(bounds.height * this.visualScale_); |
| 948 div.style.height = newHeight + 'px'; | 358 div.style.height = newHeight + 'px'; |
| 949 nameContainer.style.marginTop = | 359 nameContainer.style.marginTop = |
| 950 (newHeight - nameContainer.offsetHeight) / 2 + 'px'; | 360 (newHeight - nameContainer.offsetHeight) / 2 + 'px'; |
| 951 | 361 |
| 952 div.onmousedown = this.onMouseDown_.bind(this); | 362 if (displayLayout.parentId == '') { |
| 953 div.ontouchstart = this.onTouchStart_.bind(this); | |
| 954 | |
| 955 if (displayLayout.isPrimary) { | |
| 956 div.style.left = | 363 div.style.left = |
| 957 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; | 364 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; |
| 958 div.style.top = | 365 div.style.top = |
| 959 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; | 366 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; |
| 960 } else { | 367 } else { |
| 961 // Don't trust the secondary display's x or y, because it may cause a | 368 // Don't trust the child display's x or y, because it may cause a |
| 962 // 1px gap due to rounding, which will create a fake update on end | 369 // 1px gap due to rounding, which will create a fake update on end |
| 963 // dragging. See crbug.com/386401 | 370 // dragging. See crbug.com/386401 |
| 964 var primaryDiv = this.displayLayoutMap_[this.primaryDisplayId_].div; | 371 var parentDiv = this.displayLayoutMap_[displayLayout.parentId].div; |
| 965 switch (layoutType) { | 372 switch (displayLayout.layoutType) { |
| 966 case options.DisplayLayoutType.TOP: | 373 case options.DisplayLayoutType.TOP: |
| 967 div.style.left = | 374 div.style.left = |
| 968 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; | 375 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; |
| 969 div.style.top = primaryDiv.offsetTop - div.offsetHeight + 'px'; | 376 div.style.top = parentDiv.offsetTop - div.offsetHeight + 'px'; |
| 970 break; | 377 break; |
| 971 case options.DisplayLayoutType.RIGHT: | 378 case options.DisplayLayoutType.RIGHT: |
| 972 div.style.left = | 379 div.style.left = |
| 973 primaryDiv.offsetLeft + primaryDiv.offsetWidth + 'px'; | 380 parentDiv.offsetLeft + parentDiv.offsetWidth + 'px'; |
| 974 div.style.top = | 381 div.style.top = |
| 975 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; | 382 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; |
| 976 break; | 383 break; |
| 977 case options.DisplayLayoutType.BOTTOM: | 384 case options.DisplayLayoutType.BOTTOM: |
| 978 div.style.left = | 385 div.style.left = |
| 979 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; | 386 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; |
| 980 div.style.top = | 387 div.style.top = parentDiv.offsetTop + parentDiv.offsetHeight + 'px'; |
| 981 primaryDiv.offsetTop + primaryDiv.offsetHeight + 'px'; | |
| 982 break; | 388 break; |
| 983 case options.DisplayLayoutType.LEFT: | 389 case options.DisplayLayoutType.LEFT: |
| 984 div.style.left = primaryDiv.offsetLeft - div.offsetWidth + 'px'; | 390 div.style.left = parentDiv.offsetLeft - div.offsetWidth + 'px'; |
| 985 div.style.top = | 391 div.style.top = |
| 986 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; | 392 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; |
| 987 break; | 393 break; |
| 988 } | 394 } |
| 989 } | 395 } |
| 990 | 396 |
| 991 displayLayout.div = div; | 397 displayLayout.div = div; |
| 992 displayLayout.originalPosition.x = div.offsetLeft; | 398 displayLayout.originalPosition.x = div.offsetLeft; |
| 993 displayLayout.originalPosition.y = div.offsetTop; | 399 displayLayout.originalPosition.y = div.offsetTop; |
| 400 | |
| 401 this.calculateOffset_(displayLayout); | |
| 994 }, | 402 }, |
| 995 | 403 |
| 996 /** | 404 /** |
| 997 * Layouts the display rectangles according to the current layout_. | 405 * Calculates the offset for display |id| relative to its parent. |
| 998 * @param {options.DisplayLayoutType} layoutType | 406 * @param {options.DisplayLayout} displayLayout |
| 999 * @private | |
| 1000 */ | 407 */ |
| 1001 layoutDisplays_: function(layoutType) { | 408 calculateOffset_: function(displayLayout) { |
| 1002 var maxWidth = 0; | 409 // Offset is calculated from top or left edge. |
| 1003 var maxHeight = 0; | 410 var parent = this.displayLayoutMap_[displayLayout.parentId]; |
| 1004 var boundingBox = {left: 0, right: 0, top: 0, bottom: 0}; | 411 if (!parent) { |
| 1005 this.primaryDisplayId_ = ''; | 412 displayLayout.offset = 0; |
| 1006 this.secondaryDisplayId_ = ''; | 413 return; |
| 1007 for (var i = 0; i < this.displays_.length; i++) { | |
| 1008 var display = this.displays_[i]; | |
| 1009 if (display.isPrimary) | |
| 1010 this.primaryDisplayId_ = display.id; | |
| 1011 else if (this.secondaryDisplayId_ == '') | |
| 1012 this.secondaryDisplayId_ = display.id; | |
| 1013 | |
| 1014 var bounds = display.bounds; | |
| 1015 boundingBox.left = Math.min(boundingBox.left, bounds.left); | |
| 1016 boundingBox.right = | |
| 1017 Math.max(boundingBox.right, bounds.left + bounds.width); | |
| 1018 boundingBox.top = Math.min(boundingBox.top, bounds.top); | |
| 1019 boundingBox.bottom = | |
| 1020 Math.max(boundingBox.bottom, bounds.top + bounds.height); | |
| 1021 maxWidth = Math.max(maxWidth, bounds.width); | |
| 1022 maxHeight = Math.max(maxHeight, bounds.height); | |
| 1023 } | 414 } |
| 1024 if (this.primaryDisplayId_ == '') | 415 var offset; |
| 1025 return; | 416 if (displayLayout.layoutType == options.DisplayLayoutType.LEFT || |
| 1026 | 417 displayLayout.layoutType == options.DisplayLayoutType.RIGHT) { |
| 1027 // Make the margin around the bounding box. | 418 offset = displayLayout.div.offsetTop - parent.div.offsetTop; |
| 1028 var areaWidth = boundingBox.right - boundingBox.left + maxWidth; | 419 } else { |
| 1029 var areaHeight = boundingBox.bottom - boundingBox.top + maxHeight; | 420 offset = displayLayout.div.offsetLeft - parent.div.offsetLeft; |
| 1030 | |
| 1031 // Calculates the scale by the width since horizontal size is more strict. | |
| 1032 // TODO(mukai): Adds the check of vertical size in case. | |
| 1033 this.visualScale_ = Math.min( | |
| 1034 VISUAL_SCALE, this.displaysView_.offsetWidth / areaWidth); | |
| 1035 | |
| 1036 // Prepare enough area for thisplays_view by adding the maximum height. | |
| 1037 this.displaysView_.style.height = | |
| 1038 Math.ceil(areaHeight * this.visualScale_) + 'px'; | |
| 1039 | |
| 1040 // Centering the bounding box of the display rectangles. | |
| 1041 var offset = { | |
| 1042 x: Math.floor( | |
| 1043 this.displaysView_.offsetWidth / 2 - | |
| 1044 (boundingBox.right + boundingBox.left) * this.visualScale_ / 2), | |
| 1045 y: Math.floor( | |
| 1046 this.displaysView_.offsetHeight / 2 - | |
| 1047 (boundingBox.bottom + boundingBox.top) * this.visualScale_ / 2) | |
| 1048 }; | |
| 1049 | |
| 1050 // Layout the display rectangles. First layout the primary display and | |
| 1051 // then layout any secondary displays. | |
| 1052 this.createDisplayLayoutDiv_( | |
| 1053 this.displayLayoutMap_[this.primaryDisplayId_], layoutType, offset); | |
| 1054 for (var i = 0; i < this.displays_.length; i++) { | |
| 1055 if (!this.displays_[i].isPrimary) { | |
| 1056 this.createDisplayLayoutDiv_( | |
| 1057 this.displayLayoutMap_[this.displays_[i].id], layoutType, offset); | |
| 1058 } | |
| 1059 } | 421 } |
| 422 displayLayout.offset = Math.floor(offset / this.visualScale_); | |
| 1060 }, | 423 }, |
| 1061 | 424 |
| 1062 /** | 425 /** |
| 1063 * Called when the display arrangement has changed. | 426 * Returns the display paired with |displayLayout|, either the parent |
| 1064 * @param {options.MultiDisplayMode} mode multi display mode. | 427 * or a matching child (currently there will only be one). |
| 1065 * @param {!Array<!options.DisplayInfo>} displays The list of the display | 428 * @param {options.DisplayLayout} displayLayout |
| 1066 * information. | 429 * @return {?options.DisplayLayout} |
| 1067 * @param {options.DisplayLayoutType} layoutType The layout strategy. | |
| 1068 * @param {number} offset The offset of the secondary display. | |
| 1069 * @private | 430 * @private |
| 1070 */ | 431 */ |
| 1071 onDisplayChanged_: function(mode, displays, layoutType, offset) { | 432 getBaseLayout_(displayLayout) { |
| 1072 if (!this.visible) | 433 if (displayLayout.parentId != '') |
| 1073 return; | 434 return this.displayLayoutMap_[displayLayout.parentId]; |
| 1074 | 435 for (var childId in this.displayLayoutMap_) { |
| 1075 this.displays_ = displays; | 436 var child = this.displayLayoutMap_[childId]; |
| 1076 this.displayLayoutMap_ = {}; | 437 if (child.parentId == displayLayout.id) |
| 1077 for (var i = 0; i < displays.length; i++) { | 438 return child; |
| 1078 var display = displays[i]; | |
| 1079 this.displayLayoutMap_[display.id] = | |
| 1080 this.createDisplayLayout_(display, layoutType); | |
| 1081 } | 439 } |
| 1082 | 440 assertNotReached(); |
| 1083 var mirroring = mode == options.MultiDisplayMode.MIRRORING; | 441 return null; |
| 1084 var unifiedDesktopEnabled = mode == options.MultiDisplayMode.UNIFIED; | |
| 1085 | |
| 1086 // Focus to the first display next to the primary one when |displays_| | |
| 1087 // is updated. | |
| 1088 if (mirroring) { | |
| 1089 this.focusedId_ = ''; | |
| 1090 } else if ( | |
| 1091 this.focusedId_ == '' || this.mirroring_ != mirroring || | |
| 1092 this.unifiedDesktopEnabled_ != unifiedDesktopEnabled || | |
| 1093 this.displays_.length != displays.length) { | |
| 1094 this.focusedId_ = displays.length > 0 ? displays[0].id : ''; | |
| 1095 } | |
| 1096 | |
| 1097 this.mirroring_ = mirroring; | |
| 1098 this.unifiedDesktopEnabled_ = unifiedDesktopEnabled; | |
| 1099 | |
| 1100 this.resetDisplaysView_(); | |
| 1101 if (this.mirroring_) | |
| 1102 this.layoutMirroringDisplays_(); | |
| 1103 else | |
| 1104 this.layoutDisplays_(layoutType); | |
| 1105 | |
| 1106 $('display-options-select-mirroring').value = | |
| 1107 mirroring ? 'mirroring' : 'extended'; | |
| 1108 | |
| 1109 $('display-options-unified-desktop').hidden = | |
| 1110 !this.showUnifiedDesktopOption_; | |
| 1111 | |
| 1112 $('display-options-toggle-unified-desktop').checked = | |
| 1113 this.unifiedDesktopEnabled_; | |
| 1114 | |
| 1115 var disableUnifiedDesktopOption = | |
| 1116 (this.mirroring_ || | |
| 1117 (!this.unifiedDesktopEnabled_ && | |
| 1118 this.displays_.length == 1)); | |
| 1119 | |
| 1120 $('display-options-toggle-unified-desktop').disabled = | |
| 1121 disableUnifiedDesktopOption; | |
| 1122 | |
| 1123 this.updateSelectedDisplayDescription_(); | |
| 1124 } | 442 } |
| 1125 }; | 443 }; |
| 1126 | 444 |
| 1127 DisplayOptions.setDisplayInfo = function( | |
| 1128 mode, displays, layoutType, offset) { | |
| 1129 DisplayOptions.getInstance().onDisplayChanged_( | |
| 1130 mode, displays, layoutType, offset); | |
| 1131 }; | |
| 1132 | |
| 1133 // Export | 445 // Export |
| 1134 return { | 446 return {DisplayLayoutManager: DisplayLayoutManager}; |
| 1135 DisplayOptions: DisplayOptions | |
| 1136 }; | |
| 1137 }); | 447 }); |
| OLD | NEW |