OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
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} |
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. |
118 var diagonalSlope = rect.height / rect.width; | 63 var diagonalSlope = rect.height / rect.width; |
119 var topDownIntercept = rect.top - rect.left * diagonalSlope; | 64 var topDownIntercept = rect.top - rect.left * diagonalSlope; |
120 var bottomUpIntercept = rect.top + rect.height + rect.left * diagonalSlope; | 65 var bottomUpIntercept = rect.top + rect.height + rect.left * diagonalSlope; |
121 | 66 |
122 if (point.y > topDownIntercept + point.x * diagonalSlope) { | 67 if (point.y > topDownIntercept + point.x * diagonalSlope) { |
123 if (point.y > bottomUpIntercept - point.x * diagonalSlope) | 68 if (point.y > bottomUpIntercept - point.x * diagonalSlope) |
(...skipping 29 matching lines...) Expand all 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 divs. |
| 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 // Parent divs must be created before children, so the div for this |
| 160 // entry may have already been created. |
| 161 continue; |
| 162 } |
| 163 this.createDisplayLayoutDiv_(id, parentElement, offset); |
| 164 } |
367 }, | 165 }, |
368 | 166 |
369 /** | 167 /** |
370 * Touch move handler for dragging display rectangle. | 168 * Update the location of display |id| to |newPosition|. |
371 * @param {Event} e The touch move event. | 169 * @param {string} id |
| 170 * @param {options.DisplayPosition} newPosition |
372 * @private | 171 * @private |
373 */ | 172 */ |
374 onTouchMove_: function(e) { | 173 updatePosition: function(id, newPosition) { |
375 if (e.touches.length != 1) | 174 var displayLayout = this.displayLayoutMap_[id]; |
376 return true; | 175 var div = displayLayout.div; |
377 | 176 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; | 177 var baseDiv = baseLayout.div; |
492 | 178 |
493 newPosition.x = snapToEdge( | 179 newPosition.x = snapToEdge( |
494 newPosition.x, draggingDiv.offsetWidth, baseDiv.offsetLeft, | 180 newPosition.x, div.offsetWidth, baseDiv.offsetLeft, |
495 baseDiv.offsetWidth); | 181 baseDiv.offsetWidth); |
496 newPosition.y = snapToEdge( | 182 newPosition.y = snapToEdge( |
497 newPosition.y, draggingDiv.offsetHeight, baseDiv.offsetTop, | 183 newPosition.y, div.offsetHeight, baseDiv.offsetTop, |
498 baseDiv.offsetHeight); | 184 baseDiv.offsetHeight); |
499 | 185 |
500 /** @type {!options.DisplayPosition} */ var newCenter = { | 186 /** @type {!options.DisplayPosition} */ var newCenter = { |
501 x: newPosition.x + draggingDiv.offsetWidth / 2, | 187 x: newPosition.x + div.offsetWidth / 2, |
502 y: newPosition.y + draggingDiv.offsetHeight / 2 | 188 y: newPosition.y + div.offsetHeight / 2 |
503 }; | 189 }; |
504 | 190 |
505 /** @type {!options.DisplayBounds} */ var baseBounds = { | 191 /** @type {!options.DisplayBounds} */ var baseBounds = { |
506 left: baseDiv.offsetLeft, | 192 left: baseDiv.offsetLeft, |
507 top: baseDiv.offsetTop, | 193 top: baseDiv.offsetTop, |
508 width: baseDiv.offsetWidth, | 194 width: baseDiv.offsetWidth, |
509 height: baseDiv.offsetHeight | 195 height: baseDiv.offsetHeight |
510 }; | 196 }; |
511 | 197 |
512 var isPrimary = dragLayout.isPrimary; | 198 // This implementation considers only the case of two displays, i.e |
513 // layoutType is always stored in the secondary layout. | 199 // a single parent with one child. |
| 200 var isPrimary = displayLayout.parentId == ''; |
| 201 |
| 202 // layoutType is always stored in the child layout. |
514 var layoutType = | 203 var layoutType = |
515 isPrimary ? baseLayout.layoutType : dragLayout.layoutType; | 204 isPrimary ? baseLayout.layoutType : displayLayout.layoutType; |
516 | 205 |
517 switch (getPositionToRectangle(baseBounds, newCenter)) { | 206 switch (getPositionToRectangle(baseBounds, newCenter)) { |
518 case options.DisplayLayoutType.RIGHT: | 207 case options.DisplayLayoutType.RIGHT: |
519 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : | 208 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : |
520 options.DisplayLayoutType.RIGHT; | 209 options.DisplayLayoutType.RIGHT; |
521 break; | 210 break; |
522 case options.DisplayLayoutType.LEFT: | 211 case options.DisplayLayoutType.LEFT: |
523 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : | 212 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : |
524 options.DisplayLayoutType.LEFT; | 213 options.DisplayLayoutType.LEFT; |
525 break; | 214 break; |
526 case options.DisplayLayoutType.TOP: | 215 case options.DisplayLayoutType.TOP: |
527 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : | 216 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : |
528 options.DisplayLayoutType.TOP; | 217 options.DisplayLayoutType.TOP; |
529 break; | 218 break; |
530 case options.DisplayLayoutType.BOTTOM: | 219 case options.DisplayLayoutType.BOTTOM: |
531 layoutType = isPrimary ? options.DisplayLayoutType.TOP : | 220 layoutType = isPrimary ? options.DisplayLayoutType.TOP : |
532 options.DisplayLayoutType.BOTTOM; | 221 options.DisplayLayoutType.BOTTOM; |
533 break; | 222 break; |
534 } | 223 } |
535 | 224 |
536 if (layoutType == options.DisplayLayoutType.LEFT || | 225 if (layoutType == options.DisplayLayoutType.LEFT || |
537 layoutType == options.DisplayLayoutType.RIGHT) { | 226 layoutType == options.DisplayLayoutType.RIGHT) { |
538 if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight) | 227 if (newPosition.y > baseDiv.offsetTop + baseDiv.offsetHeight) |
539 layoutType = isPrimary ? options.DisplayLayoutType.TOP : | 228 layoutType = isPrimary ? options.DisplayLayoutType.TOP : |
540 options.DisplayLayoutType.BOTTOM; | 229 options.DisplayLayoutType.BOTTOM; |
541 else if (newPosition.y + draggingDiv.offsetHeight < baseDiv.offsetTop) | 230 else if (newPosition.y + div.offsetHeight < baseDiv.offsetTop) |
542 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : | 231 layoutType = isPrimary ? options.DisplayLayoutType.BOTTOM : |
543 options.DisplayLayoutType.TOP; | 232 options.DisplayLayoutType.TOP; |
544 } else { | 233 } else { |
545 if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth) | 234 if (newPosition.x > baseDiv.offsetLeft + baseDiv.offsetWidth) |
546 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : | 235 layoutType = isPrimary ? options.DisplayLayoutType.LEFT : |
547 options.DisplayLayoutType.RIGHT; | 236 options.DisplayLayoutType.RIGHT; |
548 else if (newPosition.x + draggingDiv.offsetWidth < baseDiv.offsetLeft) | 237 else if (newPosition.x + div.offsetWidth < baseDiv.offsetLeft) |
549 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : | 238 layoutType = isPrimary ? options.DisplayLayoutType.RIGHT : |
550 options.DisplayLayoutType.LEFT; | 239 options.DisplayLayoutType.LEFT; |
551 } | 240 } |
552 | 241 |
553 var layoutToBase; | 242 var layoutToBase; |
554 if (!isPrimary) { | 243 if (!isPrimary) { |
555 dragLayout.layoutType = layoutType; | 244 displayLayout.layoutType = layoutType; |
556 layoutToBase = layoutType; | 245 layoutToBase = layoutType; |
557 } else { | 246 } else { |
558 baseLayout.layoutType = layoutType; | 247 baseLayout.layoutType = layoutType; |
559 switch (layoutType) { | 248 switch (layoutType) { |
560 case options.DisplayLayoutType.RIGHT: | 249 case options.DisplayLayoutType.RIGHT: |
561 layoutToBase = options.DisplayLayoutType.LEFT; | 250 layoutToBase = options.DisplayLayoutType.LEFT; |
562 break; | 251 break; |
563 case options.DisplayLayoutType.LEFT: | 252 case options.DisplayLayoutType.LEFT: |
564 layoutToBase = options.DisplayLayoutType.RIGHT; | 253 layoutToBase = options.DisplayLayoutType.RIGHT; |
565 break; | 254 break; |
566 case options.DisplayLayoutType.TOP: | 255 case options.DisplayLayoutType.TOP: |
567 layoutToBase = options.DisplayLayoutType.BOTTOM; | 256 layoutToBase = options.DisplayLayoutType.BOTTOM; |
568 break; | 257 break; |
569 case options.DisplayLayoutType.BOTTOM: | 258 case options.DisplayLayoutType.BOTTOM: |
570 layoutToBase = options.DisplayLayoutType.TOP; | 259 layoutToBase = options.DisplayLayoutType.TOP; |
571 break; | 260 break; |
572 } | 261 } |
573 } | 262 } |
574 | 263 |
575 switch (layoutToBase) { | 264 switch (layoutToBase) { |
576 case options.DisplayLayoutType.RIGHT: | 265 case options.DisplayLayoutType.RIGHT: |
577 draggingDiv.style.left = | 266 div.style.left = baseDiv.offsetLeft + baseDiv.offsetWidth + 'px'; |
578 baseDiv.offsetLeft + baseDiv.offsetWidth + 'px'; | 267 div.style.top = newPosition.y + 'px'; |
579 draggingDiv.style.top = newPosition.y + 'px'; | |
580 break; | 268 break; |
581 case options.DisplayLayoutType.LEFT: | 269 case options.DisplayLayoutType.LEFT: |
582 draggingDiv.style.left = | 270 div.style.left = baseDiv.offsetLeft - div.offsetWidth + 'px'; |
583 baseDiv.offsetLeft - draggingDiv.offsetWidth + 'px'; | 271 div.style.top = newPosition.y + 'px'; |
584 draggingDiv.style.top = newPosition.y + 'px'; | |
585 break; | 272 break; |
586 case options.DisplayLayoutType.TOP: | 273 case options.DisplayLayoutType.TOP: |
587 draggingDiv.style.top = | 274 div.style.top = baseDiv.offsetTop - div.offsetHeight + 'px'; |
588 baseDiv.offsetTop - draggingDiv.offsetHeight + 'px'; | 275 div.style.left = newPosition.x + 'px'; |
589 draggingDiv.style.left = newPosition.x + 'px'; | |
590 break; | 276 break; |
591 case options.DisplayLayoutType.BOTTOM: | 277 case options.DisplayLayoutType.BOTTOM: |
592 draggingDiv.style.top = | 278 div.style.top = baseDiv.offsetTop + baseDiv.offsetHeight + 'px'; |
593 baseDiv.offsetTop + baseDiv.offsetHeight + 'px'; | 279 div.style.left = newPosition.x + 'px'; |
594 draggingDiv.style.left = newPosition.x + 'px'; | |
595 break; | 280 break; |
596 } | 281 } |
597 | |
598 return false; | |
599 }, | 282 }, |
600 | 283 |
601 /** | 284 /** |
602 * Start dragging of a display rectangle. | 285 * Called from the UI to finalize the location of display |id| after updates |
603 * @param {!HTMLElement} target The event target. | 286 * are complete (e.g. after a drag was completed). |
604 * @param {!options.DisplayPosition} eventLocation The event location. | 287 * @param {string} id |
605 * @private | 288 * @return {boolean} True if the final position differs from the original. |
606 */ | 289 */ |
607 startDragging_: function(target, eventLocation) { | 290 finalizePosition: function(id) { |
608 var oldFocusedId = this.focusedId_; | 291 // Make sure the dragging location is connected. |
609 var newFocusedId; | 292 var displayLayout = this.displayLayoutMap_[id]; |
610 var willUpdateDisplayDescription = false; | 293 var div = displayLayout.div; |
611 for (var i = 0; i < this.displays_.length; i++) { | 294 var baseLayout = this.getBaseLayout_(displayLayout); |
612 var displayLayout = this.displayLayoutMap_[this.displays_[i].id]; | 295 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 | 296 |
622 this.focusedId_ = newFocusedId; | 297 var isPrimary = displayLayout.parentId == ''; |
623 willUpdateDisplayDescription = newFocusedId != oldFocusedId; | 298 var layoutType = |
| 299 isPrimary ? baseLayout.layoutType : displayLayout.layoutType; |
624 | 300 |
625 for (var i = 0; i < this.displays_.length; i++) { | 301 // The number of pixels to share the edges between displays. |
626 var displayLayout = this.displayLayoutMap_[this.displays_[i].id]; | 302 /** @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 | 303 |
672 if (layoutType == options.DisplayLayoutType.LEFT || | 304 if (layoutType == options.DisplayLayoutType.LEFT || |
673 layoutType == options.DisplayLayoutType.RIGHT) { | 305 layoutType == options.DisplayLayoutType.RIGHT) { |
674 var top = Math.max( | 306 var top = Math.max( |
675 draggingDiv.offsetTop, | 307 div.offsetTop, |
676 baseDiv.offsetTop - draggingDiv.offsetHeight + MIN_OFFSET_OVERLAP); | 308 baseDiv.offsetTop - div.offsetHeight + MIN_OFFSET_OVERLAP); |
677 top = Math.min( | 309 top = Math.min( |
678 top, baseDiv.offsetTop + baseDiv.offsetHeight - MIN_OFFSET_OVERLAP); | 310 top, baseDiv.offsetTop + baseDiv.offsetHeight - MIN_OFFSET_OVERLAP); |
679 draggingDiv.style.top = top + 'px'; | 311 div.style.top = top + 'px'; |
680 } else { | 312 } else { |
681 var left = Math.max( | 313 var left = Math.max( |
682 draggingDiv.offsetLeft, | 314 div.offsetLeft, |
683 baseDiv.offsetLeft - draggingDiv.offsetWidth + MIN_OFFSET_OVERLAP); | 315 baseDiv.offsetLeft - div.offsetWidth + MIN_OFFSET_OVERLAP); |
684 left = Math.min( | 316 left = Math.min( |
685 left, | 317 left, |
686 baseDiv.offsetLeft + baseDiv.offsetWidth - MIN_OFFSET_OVERLAP); | 318 baseDiv.offsetLeft + baseDiv.offsetWidth - MIN_OFFSET_OVERLAP); |
687 draggingDiv.style.left = left + 'px'; | 319 div.style.left = left + 'px'; |
688 } | |
689 if (dragLayout.originalPosition.x != draggingDiv.offsetLeft || | |
690 dragLayout.originalPosition.y != draggingDiv.offsetTop) { | |
691 this.sendDragResult_(); | |
692 } | 320 } |
693 | 321 |
694 this.dragInfo_ = null; | 322 // Calculate the offset of the child display. |
| 323 this.calculateOffset_(isPrimary ? baseLayout : displayLayout); |
695 | 324 |
696 return false; | 325 return displayLayout.originalPosition.x != div.offsetLeft || |
| 326 displayLayout.originalPosition.y != div.offsetTop; |
697 }, | 327 }, |
698 | 328 |
699 /** | 329 /** |
700 * Updates the description of selected display section for mirroring mode. | 330 * Creates a div element and assigns it to |displayLayout|. Returns the |
701 * @private | 331 * created div for additional decoration. |
| 332 * @param {string} id |
| 333 * @param {!Element} parentElement The parent element to contain the div. |
| 334 * @param {!options.DisplayPosition} offset The offset to the center of |
| 335 * the display area. |
702 */ | 336 */ |
703 updateSelectedDisplaySectionMirroring_: function() { | 337 createDisplayLayoutDiv_: function(id, parentElement, offset) { |
704 $('display-configuration-arrow').hidden = true; | 338 var displayLayout = this.displayLayoutMap_[id]; |
705 $('display-options-set-primary').disabled = true; | 339 var parentId = displayLayout.parentId; |
706 $('display-options-select-mirroring').disabled = false; | 340 if (parentId) { |
707 $('selected-display-start-calibrating-overscan').disabled = true; | 341 // Ensure the parent div is created first. |
708 var display = this.displays_[0]; | 342 var parentLayout = this.displayLayoutMap_[parentId]; |
709 var orientation = $('display-options-orientation-selection'); | 343 if (!parentLayout.div) |
710 orientation.disabled = false; | 344 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 } | 345 } |
806 | 346 |
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')); | 347 var div = /** @type {!HTMLElement} */ (document.createElement('div')); |
933 div.className = 'displays-display'; | 348 div.className = 'displays-display'; |
934 div.classList.toggle( | |
935 'displays-focused', displayLayout.id == this.focusedId_); | |
936 | 349 |
937 // div needs to be added to the DOM tree first, otherwise offsetHeight for | 350 // div needs to be added to the DOM tree first, otherwise offsetHeight for |
938 // nameContainer below cannot be computed. | 351 // nameContainer below cannot be computed. |
939 this.displaysView_.appendChild(div); | 352 parentElement.appendChild(div); |
940 | 353 |
941 var nameContainer = document.createElement('div'); | 354 var nameContainer = document.createElement('div'); |
942 nameContainer.textContent = displayLayout.name; | 355 nameContainer.textContent = displayLayout.name; |
943 div.appendChild(nameContainer); | 356 div.appendChild(nameContainer); |
944 | 357 |
945 var bounds = displayLayout.bounds; | 358 var bounds = displayLayout.bounds; |
946 div.style.width = Math.floor(bounds.width * this.visualScale_) + 'px'; | 359 div.style.width = Math.floor(bounds.width * this.visualScale_) + 'px'; |
947 var newHeight = Math.floor(bounds.height * this.visualScale_); | 360 var newHeight = Math.floor(bounds.height * this.visualScale_); |
948 div.style.height = newHeight + 'px'; | 361 div.style.height = newHeight + 'px'; |
949 nameContainer.style.marginTop = | 362 nameContainer.style.marginTop = |
950 (newHeight - nameContainer.offsetHeight) / 2 + 'px'; | 363 (newHeight - nameContainer.offsetHeight) / 2 + 'px'; |
951 | 364 |
952 div.onmousedown = this.onMouseDown_.bind(this); | 365 if (displayLayout.parentId == '') { |
953 div.ontouchstart = this.onTouchStart_.bind(this); | |
954 | |
955 if (displayLayout.isPrimary) { | |
956 div.style.left = | 366 div.style.left = |
957 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; | 367 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; |
958 div.style.top = | 368 div.style.top = |
959 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; | 369 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; |
960 } else { | 370 } else { |
961 // Don't trust the secondary display's x or y, because it may cause a | 371 // 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 | 372 // 1px gap due to rounding, which will create a fake update on end |
963 // dragging. See crbug.com/386401 | 373 // dragging. See crbug.com/386401 |
964 var primaryDiv = this.displayLayoutMap_[this.primaryDisplayId_].div; | 374 var parentDiv = this.displayLayoutMap_[displayLayout.parentId].div; |
965 switch (layoutType) { | 375 switch (displayLayout.layoutType) { |
966 case options.DisplayLayoutType.TOP: | 376 case options.DisplayLayoutType.TOP: |
967 div.style.left = | 377 div.style.left = |
968 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; | 378 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; |
969 div.style.top = primaryDiv.offsetTop - div.offsetHeight + 'px'; | 379 div.style.top = parentDiv.offsetTop - div.offsetHeight + 'px'; |
970 break; | 380 break; |
971 case options.DisplayLayoutType.RIGHT: | 381 case options.DisplayLayoutType.RIGHT: |
972 div.style.left = | 382 div.style.left = |
973 primaryDiv.offsetLeft + primaryDiv.offsetWidth + 'px'; | 383 parentDiv.offsetLeft + parentDiv.offsetWidth + 'px'; |
974 div.style.top = | 384 div.style.top = |
975 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; | 385 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; |
976 break; | 386 break; |
977 case options.DisplayLayoutType.BOTTOM: | 387 case options.DisplayLayoutType.BOTTOM: |
978 div.style.left = | 388 div.style.left = |
979 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; | 389 Math.floor(bounds.left * this.visualScale_) + offset.x + 'px'; |
980 div.style.top = | 390 div.style.top = parentDiv.offsetTop + parentDiv.offsetHeight + 'px'; |
981 primaryDiv.offsetTop + primaryDiv.offsetHeight + 'px'; | |
982 break; | 391 break; |
983 case options.DisplayLayoutType.LEFT: | 392 case options.DisplayLayoutType.LEFT: |
984 div.style.left = primaryDiv.offsetLeft - div.offsetWidth + 'px'; | 393 div.style.left = parentDiv.offsetLeft - div.offsetWidth + 'px'; |
985 div.style.top = | 394 div.style.top = |
986 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; | 395 Math.floor(bounds.top * this.visualScale_) + offset.y + 'px'; |
987 break; | 396 break; |
988 } | 397 } |
989 } | 398 } |
990 | 399 |
991 displayLayout.div = div; | 400 displayLayout.div = div; |
992 displayLayout.originalPosition.x = div.offsetLeft; | 401 displayLayout.originalPosition.x = div.offsetLeft; |
993 displayLayout.originalPosition.y = div.offsetTop; | 402 displayLayout.originalPosition.y = div.offsetTop; |
| 403 |
| 404 this.calculateOffset_(displayLayout); |
994 }, | 405 }, |
995 | 406 |
996 /** | 407 /** |
997 * Layouts the display rectangles according to the current layout_. | 408 * Calculates the offset for display |id| relative to its parent. |
998 * @param {options.DisplayLayoutType} layoutType | 409 * @param {options.DisplayLayout} displayLayout |
999 * @private | |
1000 */ | 410 */ |
1001 layoutDisplays_: function(layoutType) { | 411 calculateOffset_: function(displayLayout) { |
1002 var maxWidth = 0; | 412 // Offset is calculated from top or left edge. |
1003 var maxHeight = 0; | 413 var parent = this.displayLayoutMap_[displayLayout.parentId]; |
1004 var boundingBox = {left: 0, right: 0, top: 0, bottom: 0}; | 414 if (!parent) { |
1005 this.primaryDisplayId_ = ''; | 415 displayLayout.offset = 0; |
1006 this.secondaryDisplayId_ = ''; | 416 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 } | 417 } |
1024 if (this.primaryDisplayId_ == '') | 418 var offset; |
1025 return; | 419 if (displayLayout.layoutType == options.DisplayLayoutType.LEFT || |
1026 | 420 displayLayout.layoutType == options.DisplayLayoutType.RIGHT) { |
1027 // Make the margin around the bounding box. | 421 offset = displayLayout.div.offsetTop - parent.div.offsetTop; |
1028 var areaWidth = boundingBox.right - boundingBox.left + maxWidth; | 422 } else { |
1029 var areaHeight = boundingBox.bottom - boundingBox.top + maxHeight; | 423 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 } | 424 } |
| 425 displayLayout.offset = Math.floor(offset / this.visualScale_); |
1060 }, | 426 }, |
1061 | 427 |
1062 /** | 428 /** |
1063 * Called when the display arrangement has changed. | 429 * Returns the display paired with |displayLayout|, either the parent |
1064 * @param {options.MultiDisplayMode} mode multi display mode. | 430 * or a matching child (currently there will only be one). |
1065 * @param {!Array<!options.DisplayInfo>} displays The list of the display | 431 * @param {options.DisplayLayout} displayLayout |
1066 * information. | 432 * @return {?options.DisplayLayout} |
1067 * @param {options.DisplayLayoutType} layoutType The layout strategy. | |
1068 * @param {number} offset The offset of the secondary display. | |
1069 * @private | 433 * @private |
1070 */ | 434 */ |
1071 onDisplayChanged_: function(mode, displays, layoutType, offset) { | 435 getBaseLayout_(displayLayout) { |
1072 if (!this.visible) | 436 if (displayLayout.parentId != '') |
1073 return; | 437 return this.displayLayoutMap_[displayLayout.parentId]; |
1074 | 438 for (var childId in this.displayLayoutMap_) { |
1075 this.displays_ = displays; | 439 var child = this.displayLayoutMap_[childId]; |
1076 this.displayLayoutMap_ = {}; | 440 if (child.parentId == displayLayout.id) |
1077 for (var i = 0; i < displays.length; i++) { | 441 return child; |
1078 var display = displays[i]; | |
1079 this.displayLayoutMap_[display.id] = | |
1080 this.createDisplayLayout_(display, layoutType); | |
1081 } | 442 } |
1082 | 443 assertNotReached(); |
1083 var mirroring = mode == options.MultiDisplayMode.MIRRORING; | 444 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 } | 445 } |
1125 }; | 446 }; |
1126 | 447 |
1127 DisplayOptions.setDisplayInfo = function( | |
1128 mode, displays, layoutType, offset) { | |
1129 DisplayOptions.getInstance().onDisplayChanged_( | |
1130 mode, displays, layoutType, offset); | |
1131 }; | |
1132 | |
1133 // Export | 448 // Export |
1134 return { | 449 return {DisplayLayoutManager: DisplayLayoutManager}; |
1135 DisplayOptions: DisplayOptions | |
1136 }; | |
1137 }); | 450 }); |
OLD | NEW |