| OLD | NEW |
| 1 // Copyright 2016 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 /** | 5 /** |
| 6 * @fileoverview Behavior for handling display layout, specifically | 6 * @fileoverview Behavior for handling display layout, specifically |
| 7 * edge snapping and collisions. | 7 * edge snapping and collisions. |
| 8 */ | 8 */ |
| 9 | 9 |
| 10 /** @polymerBehavior */ | 10 /** @polymerBehavior */ |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 73 * bounds for the display. | 73 * bounds for the display. |
| 74 * @return {!chrome.system.display.Bounds} | 74 * @return {!chrome.system.display.Bounds} |
| 75 */ | 75 */ |
| 76 updateDisplayBounds(id, newBounds) { | 76 updateDisplayBounds(id, newBounds) { |
| 77 this.dragLayoutId = id; | 77 this.dragLayoutId = id; |
| 78 | 78 |
| 79 // Find the closest parent. | 79 // Find the closest parent. |
| 80 var closestId = this.findClosest_(id, newBounds); | 80 var closestId = this.findClosest_(id, newBounds); |
| 81 | 81 |
| 82 // Find the closest edge. | 82 // Find the closest edge. |
| 83 var layoutPosition = this.getLayoutPositionForBounds_(newBounds, closestId); | 83 var closestBounds = this.getCalculatedDisplayBounds(closestId); |
| 84 var layoutPosition = |
| 85 this.getLayoutPositionForBounds_(newBounds, closestBounds); |
| 84 | 86 |
| 85 // Snap to the closest edge. | 87 // Snap to the closest edge. |
| 86 this.snapBounds_(closestId, layoutPosition, newBounds); | 88 var snapPos = this.snapBounds_(newBounds, closestId, layoutPosition); |
| 89 newBounds.left = snapPos.x; |
| 90 newBounds.top = snapPos.y; |
| 87 | 91 |
| 88 // Calculate the new bounds and delta. | 92 // Calculate the new bounds and delta. |
| 89 var oldBounds = this.dragBounds_ || this.getCalculatedDisplayBounds(id); | 93 var oldBounds = this.dragBounds_ || this.getCalculatedDisplayBounds(id); |
| 90 var deltaPos = { | 94 var deltaPos = { |
| 91 x: newBounds.left - oldBounds.left, | 95 x: newBounds.left - oldBounds.left, |
| 92 y: newBounds.top - oldBounds.top | 96 y: newBounds.top - oldBounds.top |
| 93 }; | 97 }; |
| 94 | 98 |
| 95 // Check for collisions after snapping. This should not collide with the | 99 // Check for collisions after snapping. This should not collide with the |
| 96 // closest parent. | 100 // closest parent. |
| (...skipping 18 matching lines...) Expand all Loading... |
| 115 /** | 119 /** |
| 116 * Called when dragging ends. Sends the updated layout to chrome. | 120 * Called when dragging ends. Sends the updated layout to chrome. |
| 117 * @param {string} id | 121 * @param {string} id |
| 118 */ | 122 */ |
| 119 finishUpdateDisplayBounds(id) { | 123 finishUpdateDisplayBounds(id) { |
| 120 this.highlightEdge_('', undefined); // Remove any highlights. | 124 this.highlightEdge_('', undefined); // Remove any highlights. |
| 121 if (id != this.dragLayoutId || !this.dragBounds_ || | 125 if (id != this.dragLayoutId || !this.dragBounds_ || |
| 122 !this.dragLayoutPosition_) { | 126 !this.dragLayoutPosition_) { |
| 123 return; | 127 return; |
| 124 } | 128 } |
| 129 |
| 125 var layout = this.displayLayoutMap_.get(id); | 130 var layout = this.displayLayoutMap_.get(id); |
| 126 if (!layout) | 131 |
| 127 return; | 132 var orphanIds; |
| 128 // Note: This updates layout in this.displayLayoutMap_ which is also the | 133 if (!layout || layout.parentId == '') { |
| 129 // entry in this.layouts. | 134 // Primary display. Set the calculated position to |dragBounds_|. |
| 130 this.updateOffsetAndPosition_( | 135 this.setCalculatedDisplayBounds_(id, this.dragBounds_); |
| 131 this.dragBounds_, this.dragLayoutPosition_, layout); | 136 |
| 137 // We cannot re-parent the primary display, so instead make all other |
| 138 // displays orphans and clear their calculated bounds. |
| 139 orphanIds = this.findChildren_(id, true /* recurse */); |
| 140 for (let o in orphanIds) |
| 141 this.calculatedBoundsMap_.delete(o); |
| 142 |
| 143 // Re-parent |dragParentId_|. It will be forced to parent to the dragged |
| 144 // display since it is the only non-orphan. |
| 145 this.reparentOrphan_(this.dragParentId_, orphanIds); |
| 146 orphanIds.splice(orphanIds.indexOf(this.dragParentId_), 1); |
| 147 } else { |
| 148 // All immediate children of |layout| will need to be re-parented. |
| 149 orphanIds = this.findChildren_(id, false /* do not recurse */); |
| 150 for (let o in orphanIds) |
| 151 this.calculatedBoundsMap_.delete(o); |
| 152 |
| 153 // When re-parenting to a descendant, also parent any immediate child to |
| 154 // drag display's current parent. |
| 155 var topLayout = this.displayLayoutMap_.get(this.dragParentId_); |
| 156 while (topLayout && topLayout.parentId != '') { |
| 157 if (topLayout.parentId == id) { |
| 158 topLayout.parentId = layout.parentId; |
| 159 break; |
| 160 } |
| 161 topLayout = this.displayLayoutMap_.get(topLayout.parentId); |
| 162 } |
| 163 |
| 164 // Re-parent the dragged display. |
| 165 layout.parentId = this.dragParentId_; |
| 166 this.updateOffsetAndPosition_( |
| 167 this.dragBounds_, this.dragLayoutPosition_, layout); |
| 168 } |
| 169 |
| 170 // Update any orphaned children. This may cause the dragged display to |
| 171 // be re-attached if it was attached to a child. |
| 172 this.updateOrphans_(orphanIds); |
| 132 | 173 |
| 133 // Send the updated layouts. | 174 // Send the updated layouts. |
| 134 chrome.system.display.setDisplayLayout(this.layouts, function() { | 175 chrome.system.display.setDisplayLayout(this.layouts, function() { |
| 135 if (chrome.runtime.lastError) { | 176 if (chrome.runtime.lastError) { |
| 136 console.error( | 177 console.error( |
| 137 'setDisplayLayout Error: ' + chrome.runtime.lastError.message); | 178 'setDisplayLayout Error: ' + chrome.runtime.lastError.message); |
| 138 } | 179 } |
| 139 }); | 180 }); |
| 140 }, | 181 }, |
| 141 | 182 |
| (...skipping 14 matching lines...) Expand all Loading... |
| 156 */ | 197 */ |
| 157 setCalculatedDisplayBounds_: function(displayId, bounds) { | 198 setCalculatedDisplayBounds_: function(displayId, bounds) { |
| 158 assert(bounds); | 199 assert(bounds); |
| 159 this.calculatedBoundsMap_.set( | 200 this.calculatedBoundsMap_.set( |
| 160 displayId, | 201 displayId, |
| 161 /** @type {!chrome.system.display.Bounds} */ ( | 202 /** @type {!chrome.system.display.Bounds} */ ( |
| 162 Object.assign({}, bounds))); | 203 Object.assign({}, bounds))); |
| 163 }, | 204 }, |
| 164 | 205 |
| 165 /** | 206 /** |
| 166 * Recursively calculate the absolute bounds of a display. | 207 * Re-parents all entries in |orphanIds| and any children. |
| 208 * @param {!Array<string>} orphanIds The list of ids affected by the move. |
| 209 * @private |
| 210 */ |
| 211 updateOrphans_: function(orphanIds) { |
| 212 var orphans = orphanIds.slice(); |
| 213 for (let orphan of orphanIds) { |
| 214 var newOrphans = this.findChildren_(orphan, true /* recurse */); |
| 215 // If the dragged display was re-parented to one of its children, |
| 216 // there may be duplicates so merge the lists. |
| 217 for (let o of newOrphans) { |
| 218 if (!orphans.includes(o)) |
| 219 orphans.push(o); |
| 220 } |
| 221 } |
| 222 |
| 223 // Remove each orphan from the list as it is re-parented so that |
| 224 // subsequent orphans can be parented to it. |
| 225 while (orphans.length) { |
| 226 var orphanId = orphans.shift(); |
| 227 this.reparentOrphan_(orphanId, orphans); |
| 228 } |
| 229 }, |
| 230 |
| 231 /** |
| 232 * Re-parents the orphan to a layout that is not a member of |
| 233 * |otherOrphanIds|. |
| 234 * @param {string} orphanId The id of the orphan to re-parent. |
| 235 * @param {Array<string>} otherOrphanIds The list of ids of other orphans |
| 236 * to ignore when re-parenting. |
| 237 * @private |
| 238 */ |
| 239 reparentOrphan_: function(orphanId, otherOrphanIds) { |
| 240 var layout = this.displayLayoutMap_.get(orphanId); |
| 241 assert(layout); |
| 242 if (orphanId == this.dragId_ && layout.parentId != '') { |
| 243 this.setCalculatedDisplayBounds_(orphanId, this.dragBounds_); |
| 244 return; |
| 245 } |
| 246 var bounds = this.getCalculatedDisplayBounds(orphanId); |
| 247 |
| 248 // Find the closest parent. |
| 249 var newParentId = this.findClosest_(orphanId, bounds, otherOrphanIds); |
| 250 assert(newParentId != ''); |
| 251 layout.parentId = newParentId; |
| 252 |
| 253 // Find the closest edge. |
| 254 var parentBounds = this.getCalculatedDisplayBounds(newParentId); |
| 255 var layoutPosition = this.getLayoutPositionForBounds_(bounds, parentBounds); |
| 256 |
| 257 // Move from the nearest corner to the desired location and get the delta. |
| 258 var cornerBounds = this.getCornerBounds_(bounds, parentBounds); |
| 259 var desiredPos = this.snapBounds_(bounds, newParentId, layoutPosition); |
| 260 var deltaPos = { |
| 261 x: desiredPos.x - cornerBounds.left, |
| 262 y: desiredPos.y - cornerBounds.top |
| 263 }; |
| 264 |
| 265 // Check for collisions. |
| 266 this.collideAndModifyDelta_(orphanId, cornerBounds, deltaPos); |
| 267 var desiredBounds = { |
| 268 left: cornerBounds.left + deltaPos.x, |
| 269 top: cornerBounds.top + deltaPos.y, |
| 270 width: bounds.width, |
| 271 height: bounds.height |
| 272 }; |
| 273 |
| 274 this.updateOffsetAndPosition_(desiredBounds, layoutPosition, layout); |
| 275 }, |
| 276 |
| 277 /** |
| 278 * @param {string} parentId |
| 279 * @param {boolean} recurse Include descendants of children. |
| 280 * @return {!Array<string>} |
| 281 * @private |
| 282 */ |
| 283 findChildren_: function(parentId, recurse) { |
| 284 var children = []; |
| 285 for (let childId of this.displayLayoutMap_.keys()) { |
| 286 if (childId == parentId) |
| 287 continue; |
| 288 if (this.displayLayoutMap_.get(childId).parentId == parentId) { |
| 289 // Insert immediate children at the front of the array. |
| 290 children.unshift(childId); |
| 291 if (recurse) { |
| 292 // Descendants get added to the end of the list. |
| 293 children = children.concat(this.findChildren_(childId, true)); |
| 294 } |
| 295 } |
| 296 } |
| 297 return children; |
| 298 }, |
| 299 |
| 300 /** |
| 301 * Recursively calculates the absolute bounds of a display. |
| 167 * Caches the display bounds so that parent bounds are only calculated once. | 302 * Caches the display bounds so that parent bounds are only calculated once. |
| 168 * @param {string} id | 303 * @param {string} id |
| 169 * @param {number} width | 304 * @param {number} width |
| 170 * @param {number} height | 305 * @param {number} height |
| 171 * @private | 306 * @private |
| 172 */ | 307 */ |
| 173 calculateBounds_: function(id, width, height) { | 308 calculateBounds_: function(id, width, height) { |
| 174 var left, top; | 309 var left, top; |
| 175 var layout = this.displayLayoutMap_.get(id); | 310 var layout = this.displayLayoutMap_.get(id); |
| 176 if (!layout || !layout.parentId) { | 311 if (!layout || !layout.parentId) { |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 255 closestId = otherId; | 390 closestId = otherId; |
| 256 closestDelta2 = delta2; | 391 closestDelta2 = delta2; |
| 257 } | 392 } |
| 258 } | 393 } |
| 259 return closestId; | 394 return closestId; |
| 260 }, | 395 }, |
| 261 | 396 |
| 262 /** | 397 /** |
| 263 * Calculates the LayoutPosition for |bounds| relative to |parentId|. | 398 * Calculates the LayoutPosition for |bounds| relative to |parentId|. |
| 264 * @param {!chrome.system.display.Bounds} bounds | 399 * @param {!chrome.system.display.Bounds} bounds |
| 265 * @param {string} parentId | 400 * @param {!chrome.system.display.Bounds} parentBounds |
| 266 * @return {!chrome.system.display.LayoutPosition} | 401 * @return {!chrome.system.display.LayoutPosition} |
| 267 */ | 402 */ |
| 268 getLayoutPositionForBounds_: function(bounds, parentId) { | 403 getLayoutPositionForBounds_: function(bounds, parentBounds) { |
| 269 // Translate bounds from top-left to center. | 404 // Translate bounds from top-left to center. |
| 270 var x = bounds.left + bounds.width / 2; | 405 var x = bounds.left + bounds.width / 2; |
| 271 var y = bounds.top + bounds.height / 2; | 406 var y = bounds.top + bounds.height / 2; |
| 272 | 407 |
| 273 // Determine the distance from the new bounds to both of the near edges. | 408 // Determine the distance from the new bounds to both of the near edges. |
| 274 var parentBounds = this.getCalculatedDisplayBounds(parentId); | |
| 275 var left = parentBounds.left; | 409 var left = parentBounds.left; |
| 276 var top = parentBounds.top; | 410 var top = parentBounds.top; |
| 277 var width = parentBounds.width; | 411 var width = parentBounds.width; |
| 278 var height = parentBounds.height; | 412 var height = parentBounds.height; |
| 279 | 413 |
| 280 // Signed deltas to the center of the div. | 414 // Signed deltas to the center. |
| 281 var dx = x - (left + width / 2); | 415 var dx = x - (left + width / 2); |
| 282 var dy = y - (top + height / 2); | 416 var dy = y - (top + height / 2); |
| 283 | 417 |
| 284 // Unsigned distance to each edge. | 418 // Unsigned distance to each edge. |
| 285 var distx = Math.abs(dx) - width / 2; | 419 var distx = Math.abs(dx) - width / 2; |
| 286 var disty = Math.abs(dy) - height / 2; | 420 var disty = Math.abs(dy) - height / 2; |
| 287 | 421 |
| 288 if (distx > disty) { | 422 if (distx > disty) { |
| 289 if (dx < 0) | 423 if (dx < 0) |
| 290 return chrome.system.display.LayoutPosition.LEFT; | 424 return chrome.system.display.LayoutPosition.LEFT; |
| 291 else | 425 else |
| 292 return chrome.system.display.LayoutPosition.RIGHT; | 426 return chrome.system.display.LayoutPosition.RIGHT; |
| 293 } else { | 427 } else { |
| 294 if (dy < 0) | 428 if (dy < 0) |
| 295 return chrome.system.display.LayoutPosition.TOP; | 429 return chrome.system.display.LayoutPosition.TOP; |
| 296 else | 430 else |
| 297 return chrome.system.display.LayoutPosition.BOTTOM; | 431 return chrome.system.display.LayoutPosition.BOTTOM; |
| 298 } | 432 } |
| 299 }, | 433 }, |
| 300 | 434 |
| 301 /** | 435 /** |
| 302 * Modifes |bounds| to the position closest to it along the edge of |parentId| | 436 * Modifies |bounds| to the position closest to it along the edge of |
| 303 * specified by |layoutPosition|. | 437 * |parentId| specified by |layoutPosition|. |
| 438 * @param {!chrome.system.display.Bounds} bounds |
| 304 * @param {string} parentId | 439 * @param {string} parentId |
| 305 * @param {!chrome.system.display.LayoutPosition} layoutPosition | 440 * @param {!chrome.system.display.LayoutPosition} layoutPosition |
| 306 * @param {!chrome.system.display.Bounds} bounds | 441 * @return {!{x: number, y: number}} |
| 307 */ | 442 */ |
| 308 snapBounds_: function(parentId, layoutPosition, bounds) { | 443 snapBounds_: function(bounds, parentId, layoutPosition) { |
| 309 var parentBounds = this.getCalculatedDisplayBounds(parentId); | 444 var parentBounds = this.getCalculatedDisplayBounds(parentId); |
| 310 | 445 |
| 311 var x; | 446 var x; |
| 312 if (layoutPosition == chrome.system.display.LayoutPosition.LEFT) { | 447 if (layoutPosition == chrome.system.display.LayoutPosition.LEFT) { |
| 313 x = parentBounds.left - bounds.width; | 448 x = parentBounds.left - bounds.width; |
| 314 } else if (layoutPosition == chrome.system.display.LayoutPosition.RIGHT) { | 449 } else if (layoutPosition == chrome.system.display.LayoutPosition.RIGHT) { |
| 315 x = parentBounds.left + parentBounds.width; | 450 x = parentBounds.left + parentBounds.width; |
| 316 } else { | 451 } else { |
| 317 x = this.snapToX_(bounds, parentBounds); | 452 x = this.snapToX_(bounds, parentBounds); |
| 318 } | 453 } |
| 319 | 454 |
| 320 var y; | 455 var y; |
| 321 if (layoutPosition == chrome.system.display.LayoutPosition.TOP) { | 456 if (layoutPosition == chrome.system.display.LayoutPosition.TOP) { |
| 322 y = parentBounds.top - bounds.height; | 457 y = parentBounds.top - bounds.height; |
| 323 } else if (layoutPosition == chrome.system.display.LayoutPosition.BOTTOM) { | 458 } else if (layoutPosition == chrome.system.display.LayoutPosition.BOTTOM) { |
| 324 y = parentBounds.top + parentBounds.height; | 459 y = parentBounds.top + parentBounds.height; |
| 325 } else { | 460 } else { |
| 326 y = this.snapToY_(bounds, parentBounds); | 461 y = this.snapToY_(bounds, parentBounds); |
| 327 } | 462 } |
| 328 | 463 |
| 329 bounds.left = x; | 464 return {x: x, y: y}; |
| 330 bounds.top = y; | |
| 331 }, | 465 }, |
| 332 | 466 |
| 333 /** | 467 /** |
| 334 * Snaps a horizontal value, see snapToEdge. | 468 * Snaps a horizontal value, see snapToEdge. |
| 335 * @param {!chrome.system.display.Bounds} newBounds | 469 * @param {!chrome.system.display.Bounds} newBounds |
| 336 * @param {!chrome.system.display.Bounds} parentBounds | 470 * @param {!chrome.system.display.Bounds} parentBounds |
| 337 * @param {number=} opt_snapDistance Provide to override the snap distance. | 471 * @param {number=} opt_snapDistance Provide to override the snap distance. |
| 338 * 0 means snap from any distance. | 472 * 0 means snap from any distance. |
| 339 * @return {number} | 473 * @return {number} |
| 340 */ | 474 */ |
| (...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 503 /** @const */ var MIN_OFFSET_OVERLAP = 50; | 637 /** @const */ var MIN_OFFSET_OVERLAP = 50; |
| 504 minOffset += MIN_OFFSET_OVERLAP; | 638 minOffset += MIN_OFFSET_OVERLAP; |
| 505 maxOffset -= MIN_OFFSET_OVERLAP; | 639 maxOffset -= MIN_OFFSET_OVERLAP; |
| 506 layout.offset = Math.max(minOffset, Math.min(offset, maxOffset)); | 640 layout.offset = Math.max(minOffset, Math.min(offset, maxOffset)); |
| 507 | 641 |
| 508 // Update the calculated bounds to match the new offset. | 642 // Update the calculated bounds to match the new offset. |
| 509 this.calculateBounds_(layout.id, bounds.width, bounds.height); | 643 this.calculateBounds_(layout.id, bounds.width, bounds.height); |
| 510 }, | 644 }, |
| 511 | 645 |
| 512 /** | 646 /** |
| 647 * Returns |bounds| translated to touch the closest corner of |parentBounds|. |
| 648 * @param {!chrome.system.display.Bounds} bounds |
| 649 * @param {!chrome.system.display.Bounds} parentBounds |
| 650 * @return {!chrome.system.display.Bounds} |
| 651 * @private |
| 652 */ |
| 653 getCornerBounds_: function(bounds, parentBounds) { |
| 654 var x; |
| 655 if (bounds.left > parentBounds.left + parentBounds.width / 2) |
| 656 x = parentBounds.left + parentBounds.width; |
| 657 else |
| 658 x = parentBounds.left - bounds.width; |
| 659 var y; |
| 660 if (bounds.top > parentBounds.top + parentBounds.height / 2) |
| 661 y = parentBounds.top + parentBounds.height; |
| 662 else |
| 663 y = parentBounds.top - bounds.height; |
| 664 return { |
| 665 left: x, |
| 666 top: y, |
| 667 width: bounds.width, |
| 668 height: bounds.height, |
| 669 }; |
| 670 }, |
| 671 |
| 672 /** |
| 513 * Highlights the edge of the div associated with |id| based on | 673 * Highlights the edge of the div associated with |id| based on |
| 514 * |layoutPosition| and removes any other highlights. If |layoutPosition| is | 674 * |layoutPosition| and removes any other highlights. If |layoutPosition| is |
| 515 * undefined, removes all highlights. | 675 * undefined, removes all highlights. |
| 516 * @param {string} id | 676 * @param {string} id |
| 517 * @param {chrome.system.display.LayoutPosition|undefined} layoutPosition | 677 * @param {chrome.system.display.LayoutPosition|undefined} layoutPosition |
| 518 * @private | 678 * @private |
| 519 */ | 679 */ |
| 520 highlightEdge_: function(id, layoutPosition) { | 680 highlightEdge_: function(id, layoutPosition) { |
| 521 for (let layout of this.layouts) { | 681 for (let layout of this.layouts) { |
| 522 var highlight = (layout.id == id) ? layoutPosition : undefined; | 682 var highlight = (layout.id == id) ? layoutPosition : undefined; |
| 523 var div = this.$$('#_' + layout.id); | 683 var div = this.$$('#_' + layout.id); |
| 524 div.classList.toggle( | 684 div.classList.toggle( |
| 525 'highlight-right', | 685 'highlight-right', |
| 526 highlight == chrome.system.display.LayoutPosition.RIGHT); | 686 highlight == chrome.system.display.LayoutPosition.RIGHT); |
| 527 div.classList.toggle( | 687 div.classList.toggle( |
| 528 'highlight-left', | 688 'highlight-left', |
| 529 highlight == chrome.system.display.LayoutPosition.LEFT); | 689 highlight == chrome.system.display.LayoutPosition.LEFT); |
| 530 div.classList.toggle( | 690 div.classList.toggle( |
| 531 'highlight-top', | 691 'highlight-top', |
| 532 highlight == chrome.system.display.LayoutPosition.TOP); | 692 highlight == chrome.system.display.LayoutPosition.TOP); |
| 533 div.classList.toggle( | 693 div.classList.toggle( |
| 534 'highlight-bottom', | 694 'highlight-bottom', |
| 535 highlight == chrome.system.display.LayoutPosition.BOTTOM); | 695 highlight == chrome.system.display.LayoutPosition.BOTTOM); |
| 536 } | 696 } |
| 537 }, | 697 }, |
| 538 }; | 698 }; |
| OLD | NEW |