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 |