Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(326)

Side by Side Diff: chrome/browser/resources/settings/device_page/layout_behavior.js

Issue 2090953007: MD Settings: Display: Reparent children of dragged displays (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@issue_547080_display_settings8b_collide
Patch Set: Rebase Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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 };
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698