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