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

Side by Side Diff: chrome/browser/resources/options/chromeos/display_layout_manager_multi.js

Issue 1649173002: Add DisplayLayoutManagerMulti (WIP) (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@issue_576375_display3e
Patch Set: Rebase Created 4 years, 10 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
OLDNEW
(Empty)
1 /**
2 * @fileoverview Description of this file.
3 */
4 // Copyright 2016 The Chromium Authors. All rights reserved.
5 // Use of this source code is governed by a BSD-style license that can be
6 // found in the LICENSE file.
7
8 cr.exportPath('options');
9
10 cr.define('options', function() {
11 'use strict';
12
13 var DisplayLayoutManager = options.DisplayLayoutManager;
14
15 var sIdPrefix = 'fakeId';
16
17 var gFakeDisplayNum = 3;
18 var gFakeDisplays = [];
19
20 /**
21 * @constructor
22 * @extends {options.DisplayLayoutManager}
23 */
24 function DisplayLayoutManagerMulti() {
25 DisplayLayoutManager.call(this);
26 }
27
28 // Helper class for display layout management. Implements logic for laying
29 // out 3+ displays.
30 DisplayLayoutManagerMulti.prototype = {
31 __proto__: DisplayLayoutManager.prototype,
32
33 /** @type {string} */
34 dragId_: '',
35
36 /** @type {string} */
37 dragParentId_: '',
38
39 /** @type {options.DisplayLayoutType} */
40 dragLayoutType_: options.DisplayLayoutType.RIGHT,
41
42 /** @override */
43 createDisplayArea: function(displayAreaDiv, minVisualScale) {
44 // Add fake displays just before creating divs.
45 this.createFakeDisplays_(gFakeDisplayNum);
46 for (var i = 0; i < gFakeDisplayNum; ++i) {
47 var fake = gFakeDisplays[i];
48 this.displayLayoutMap_[fake.id] = fake;
49 }
50 return DisplayLayoutManager.prototype.createDisplayArea.call(
51 this, displayAreaDiv, minVisualScale);
52 },
53
54 /** @override */
55 setFocusedId: function(focusedId) {
56 DisplayLayoutManager.prototype.setFocusedId.call(this, focusedId);
57 var layout = this.displayLayoutMap_[focusedId];
58 this.highlightParentEdge_(layout.parentId, layout.layoutType);
59 },
60
61 /** @override */
62 updatePosition: function(id, newPosition) {
63 this.dragId_ = id;
64 var layout = this.displayLayoutMap_[id];
65 this.dragParentId_ = this.findClosest_(layout, newPosition);
66 var parent = this.displayLayoutMap_[this.dragParentId_];
67 this.dragLayoutType_ =
68 this.getLayoutTypeForPosition_(layout.div, parent.div, newPosition);
69 this.updateDivPosition_(
70 layout.div, newPosition, parent.div, this.dragLayoutType_);
71 this.highlightParentEdge_(this.dragParentId_, this.dragLayoutType_);
72 },
73
74 /** @override */
75 finalizePosition: function(id) {
76 if (id != this.dragId_)
77 return false;
78
79 var layout = this.displayLayoutMap_[id];
80 var parent = this.displayLayoutMap_[this.dragParentId_];
81
82 // All immediate children of |layout| need to be re-parented.
83 var orphans = this.findChildren_(id, false /* do not recurse */);
84
85 // Special case: re-parenting to a descendant. Parent the immediate child
86 // to the dragged display's parent and treat that as the moved layout.
87 var newParent = parent;
88 while (newParent) {
89 if (newParent.parentId == '')
90 break;
91 if (newParent.parentId == id) {
92 newParent.parentId = layout.parentId;
93 break;
94 }
95 newParent = this.displayLayoutMap_[newParent.parentId];
96 }
97
98 // Re-parent the dragged display.
99 layout.parentId = this.dragParentId_;
100 layout.layoutType = this.dragLayoutType_;
101
102 // Snap to corners and calculate the offset from the new parent. This does
103 // not depend on any unresolved child layout.
104 this.adjustCorners_(layout.div, parent.div, this.dragLayoutType_);
105 this.calculateOffset_(layout);
106
107 // Update any orphaned children, which may include |id|.
108 this.updateOrphans_(orphans);
109
110 // Update the fake displays.
111 for (var i = 0; i < gFakeDisplayNum; ++i) {
112 var fakeId = gFakeDisplays[i].id;
113 var fake = Object.assign({}, this.displayLayoutMap_[fakeId]);
114 gFakeDisplays[i] = fake;
115 fake.bounds = this.calcLayoutBounds_(fake);
116 }
117
118 return layout.originalDivOffsets.x != layout.div.offsetLeft ||
119 layout.originalDivOffsets.y != layout.div.offsetTop;
120 },
121
122 /**
123 * @param {Array<string>=} orphanedIds The list of ids affected by the move.
124 * @private
125 */
126 updateOrphans_(orphanedIds) {
127 var orphans = orphanedIds.slice();
128 for (var orphan of orphanedIds) {
129 var newOrphans = this.findChildren_(orphan, true /* recurse */);
130 // If the dragged display was re-parented to one of its children,
131 // there may be duplicates so merge the lists.
132 for (var o of newOrphans) {
133 if (!orphans.includes(o))
134 orphans.push(o);
135 }
136 }
137
138 // Re-parent each orphan to a layout that is not a member of |orphans|.
139 // We remove each orphan from the list so that subsequent orphans can be
140 // parented to initial (primary) orphans.
141 while (orphans.length) {
142 var childId = orphans.shift();
143 var child = this.displayLayoutMap_[childId];
144
145 if (childId == this.dragId_) {
146 // Preserve the layout and offset of the dragged div.
147 this.calcLayoutBounds_(child);
148 this.layoutDivFromBounds_(child);
149 continue;
150 }
151
152 var pos = {x: child.div.offsetLeft, y: child.div.offsetTop};
153 var newParentId = this.findClosest_(child, pos, orphans);
154 child.parentId = newParentId;
155 // If all displays are orphans, newParentId will be empty.
156 if (newParentId != '') {
157 var parent = this.displayLayoutMap_[newParentId];
158 var layoutType =
159 this.getLayoutTypeForPosition_(child.div, parent.div, pos);
160 this.updateDivPosition_(child.div, pos, parent.div, layoutType);
161 this.adjustCorners_(child.div, parent.div, child.layoutType);
162 this.calculateOffset_(child);
163 }
164 // TODO(stevenjb): Set bounds and send child update.
165 }
166 },
167
168 /**
169 * @param {string} parentId
170 * @param {boolean} recurse Include descendants of children.
171 * @return {!Array<string>}
172 * @private
173 */
174 findChildren_(parentId, recurse) {
175 var children = [];
176 for (var childId in this.displayLayoutMap_) {
177 if (childId == parentId)
178 continue;
179 if (this.displayLayoutMap_[childId].parentId == parentId) {
180 // Insert immediate children at the front of the array.
181 children.unshift(childId);
182 if (recurse) {
183 // Descendants get added to the end of the list.
184 children = children.concat(this.findChildren_(childId, true));
185 }
186 }
187 }
188 return children;
189 },
190
191 /**
192 * Finds the display closest to |position| ignoring |child|.
193 * @param {options.DisplayLayout} child
194 * @param {options.DisplayPosition} position
195 * @param {Array<string>=} opt_ignore Ids to ignore.
196 * @return {string}
197 * @private
198 */
199 findClosest_: function(child, position, opt_ignore) {
200 var closestDist = undefined;
201 var x = position.x + child.div.offsetWidth / 2;
202 var y = position.y + child.div.offsetHeight / 2;
203 var closestId = '', closestDelta2;
204 for (var id in this.displayLayoutMap_) {
205 if (id == child.id)
206 continue;
207 if (opt_ignore && opt_ignore.includes(id))
208 continue;
209 var div = this.displayLayoutMap_[id].div;
210 var left = div.offsetLeft;
211 var top = div.offsetTop;
212 var width = div.offsetWidth;
213 var height = div.offsetHeight;
214 if (x >= left && x < left + width && y >= top && y < top + height)
215 return id; // point is inside rect
216 var dx, dy;
217 if (x < left)
218 dx = left - x;
219 else if (x > left + width)
220 dx = x - (left + width);
221 else
222 dx = 0;
223 if (y < top)
224 dy = top - y;
225 else if (y > top + height)
226 dy = y - (top + height);
227 else
228 dy = 0;
229 var delta2 = dx * dx + dy * dy;
230 if (closestId == '' || delta2 < closestDelta2) {
231 closestId = id;
232 closestDelta2 = delta2;
233 }
234 }
235 return closestId;
236 },
237
238 /**
239 * Calculates the layoutType for |position| relative to |parentDiv|.
240 * @param {?HTMLElement} div
241 * @param {?HTMLElement} parentDiv
242 * @param {options.DisplayPosition} position
243 * @return {options.DisplayLayoutType}
244 * @private
245 */
246 getLayoutTypeForPosition_: function(div, parentDiv, position) {
247 // Translate position from top-left to center.
248 var x = position.x + div.offsetWidth / 2;
249 var y = position.y + div.offsetHeight / 2;
250
251 // Determine the distance from the new position to both of the near edges.
252 var div = parentDiv;
253 var left = div.offsetLeft;
254 var top = div.offsetTop;
255 var width = div.offsetWidth;
256 var height = div.offsetHeight;
257 // Signed deltas to center.
258 var dx = x - (left + width / 2);
259 var dy = y - (top + height / 2);
260 // Unsigned distance to each edge.
261 var distx = Math.abs(dx) - width / 2;
262 var disty = Math.abs(dy) - height / 2;
263 if (distx > disty) {
264 if (dx < 0)
265 return options.DisplayLayoutType.LEFT;
266 else
267 return options.DisplayLayoutType.RIGHT;
268 } else {
269 if (dy < 0)
270 return options.DisplayLayoutType.TOP;
271 else
272 return options.DisplayLayoutType.BOTTOM;
273 }
274 },
275
276 /**
277 * Update the location |div| to the position closest to |newPosition| along
278 * the edge of |parentDiv| specified by |layoutType|.
279 * @param {?HTMLElement} div
280 * @param {options.DisplayPosition} newPosition
281 * @param {?HTMLElement} parentDiv
282 * @param {!options.DisplayLayoutType} layoutType
283 * @private
284 */
285 updateDivPosition_(div, newPosition, parentDiv, layoutType) {
286 var snapX = (layoutType == options.DisplayLayoutType.LEFT ||
287 layoutType == options.DisplayLayoutType.RIGHT) ?
288 0 /* infinite */ :
289 undefined /* default */;
290 var snapY = (layoutType == options.DisplayLayoutType.TOP ||
291 layoutType == options.DisplayLayoutType.BOTTOM) ?
292 0 /* infinite */ :
293 undefined /* default */;
294 newPosition.x = this.snapToEdge_(
295 newPosition.x, div.offsetWidth, parentDiv.offsetLeft,
296 parentDiv.offsetWidth, snapX);
297 newPosition.y = this.snapToEdge_(
298 newPosition.y, div.offsetHeight, parentDiv.offsetTop,
299 parentDiv.offsetHeight, snapY);
300
301 this.setDivPosition_(div, newPosition, parentDiv, layoutType);
302 },
303
304 /**
305 * Highlights the edge of the div associated with |parentId| based on
306 * |layoutType|.
307 * @param {string} parentId
308 * @param {options.DisplayLayoutType} layoutType
309 * @private
310 */
311 highlightParentEdge_(parentId, layoutType) {
312 for (var tid in this.displayLayoutMap_) {
313 var tlayout = this.displayLayoutMap_[tid];
314 var highlight = '';
315 if (tlayout.id == parentId) {
316 switch (layoutType) {
317 case options.DisplayLayoutType.RIGHT:
318 highlight = 'displays-parent-right';
319 break;
320 case options.DisplayLayoutType.LEFT:
321 highlight = 'displays-parent-left';
322 break;
323 case options.DisplayLayoutType.TOP:
324 highlight = 'displays-parent-top';
325 break;
326 case options.DisplayLayoutType.BOTTOM:
327 highlight = 'displays-parent-bottom';
328 break;
329 }
330 }
331 tlayout.div.classList.toggle(
332 'displays-parent-right', highlight == 'displays-parent-right');
333 tlayout.div.classList.toggle(
334 'displays-parent-left', highlight == 'displays-parent-left');
335 tlayout.div.classList.toggle(
336 'displays-parent-top', highlight == 'displays-parent-top');
337 tlayout.div.classList.toggle(
338 'displays-parent-bottom', highlight == 'displays-parent-bottom');
339 }
340 },
341
342 createFakeDisplays_: function(num) {
343 if (num != gFakeDisplayNum)
344 gFakeDisplays = [];
345 var primary;
346 for (var id in this.displayLayoutMap_) {
347 var layout = this.displayLayoutMap_[id];
348 if (layout.parentId == '')
349 primary = layout;
350 }
351 for (var i = 0; i < num; ++i) {
352 if (i < gFakeDisplays.length) {
353 gFakeDisplays[i].div = null;
354 } else {
355 var fakeId = sIdPrefix + i;
356 var layoutType = /** @type {options.DisplayLayoutType} */ (i % 4);
357 var fakeDisplayLayout = this.createDisplayLayout(
358 fakeId, 'Fake Display ' + i, primary.bounds, layoutType,
359 primary.id);
360 fakeDisplayLayout.bounds = this.calcLayoutBounds_(fakeDisplayLayout);
361 gFakeDisplays.push(fakeDisplayLayout);
362 }
363 }
364 },
365
366 calcLayoutBounds_: function(layout) {
367 if (layout.parentId == '')
368 return layout.bounds;
369 var parent = this.displayLayoutMap_[layout.parentId];
370 // Parent layout bounds may not be calculated yet, so calculate (but
371 // do not set) them.
372 var parentBounds = this.calcLayoutBounds_(parent);
373 var left = 0, top = 0;
374 switch (layout.layoutType) {
375 case options.DisplayLayoutType.TOP:
376 left = parentBounds.left + layout.offset;
377 top = parentBounds.top - layout.bounds.height;
378 break;
379 case options.DisplayLayoutType.RIGHT:
380 left = parentBounds.left + parentBounds.width;
381 top = parentBounds.top + layout.offset;
382 break;
383 case options.DisplayLayoutType.BOTTOM:
384 left = parentBounds.left + layout.offset;
385 top = parentBounds.top + parentBounds.height;
386 break;
387 case options.DisplayLayoutType.LEFT:
388 left = parentBounds.left - layout.bounds.width;
389 top = parentBounds.top + layout.offset;
390 break;
391 }
392 return {
393 left: left,
394 top: top,
395 width: layout.bounds.width,
396 height: layout.bounds.height
397 };
398 }
399
400 };
401
402 // Export
403 return {DisplayLayoutManagerMulti: DisplayLayoutManagerMulti};
404 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698