/[chrome]/trunk/src/chrome/browser/resources/ntp4/tile_page.js
Chromium logo

Annotation of /trunk/src/chrome/browser/resources/ntp4/tile_page.js

Parent Directory Parent Directory | Revision Log Revision Log


Revision 101031 - (hide annotations)
Wed Sep 14 04:55:32 2011 UTC (12 years, 7 months ago) by dbeam@chromium.org
File MIME type: application/javascript
File size: 37325 byte(s)
Normalizing .dropEffect across platforms (specifically Windows).

R=csilv@chromium.org
BUG=95719
TEST=Make sure no more "worm holes" happen on NTP4.


Review URL: http://codereview.chromium.org/7889002
1 estade@chromium.org 81845 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2     // Use of this source code is governed by a BSD-style license that can be
3     // found in the LICENSE file.
4    
5     cr.define('ntp4', function() {
6     'use strict';
7    
8 estade@chromium.org 86638 // We can't pass the currently dragging tile via dataTransfer because of
9     // http://crbug.com/31037
10     var currentlyDraggingTile = null;
11     function getCurrentlyDraggingTile() {
12     return currentlyDraggingTile;
13     }
14 estade@chromium.org 87727 function setCurrentlyDraggingTile(tile) {
15     currentlyDraggingTile = tile;
16     if (tile)
17     ntp4.enterRearrangeMode();
18     else
19     ntp4.leaveRearrangeMode();
20     }
21 estade@chromium.org 86638
22 estade@chromium.org 81845 /**
23 estade@chromium.org 86638 * Creates a new Tile object. Tiles wrap content on a TilePage, providing
24 estade@chromium.org 83385 * some styling and drag functionality.
25     * @constructor
26     * @extends {HTMLDivElement}
27     */
28     function Tile(contents) {
29     var tile = cr.doc.createElement('div');
30     tile.__proto__ = Tile.prototype;
31     tile.initialize(contents);
32    
33     return tile;
34     }
35    
36     Tile.prototype = {
37     __proto__: HTMLDivElement.prototype,
38    
39     initialize: function(contents) {
40 estade@chromium.org 83887 // 'real' as opposed to doppleganger.
41     this.className = 'tile real';
42 estade@chromium.org 83385 this.appendChild(contents);
43     contents.tile = this;
44    
45     this.addEventListener('dragstart', this.onDragStart_);
46     this.addEventListener('drag', this.onDragMove_);
47     this.addEventListener('dragend', this.onDragEnd_);
48 estade@chromium.org 83887
49 estade@chromium.org 91606 this.firstChild.addEventListener(
50     'webkitAnimationEnd', this.onContentsAnimationEnd_.bind(this));
51    
52 estade@chromium.org 84700 this.eventTracker = new EventTracker();
53 estade@chromium.org 83385 },
54    
55     get index() {
56     return Array.prototype.indexOf.call(this.parentNode.children, this);
57     },
58    
59 estade@chromium.org 84700 get tilePage() {
60     return findAncestorByClass(this, 'tile-page');
61     },
62    
63 estade@chromium.org 83385 /**
64     * Position the tile at |x, y|, and store this as the grid location, i.e.
65     * where the tile 'belongs' when it's not being dragged.
66     * @param {number} x The x coordinate, in pixels.
67     * @param {number} y The y coordinate, in pixels.
68     */
69     setGridPosition: function(x, y) {
70     this.gridX = x;
71     this.gridY = y;
72     this.moveTo(x, y);
73     },
74    
75     /**
76     * Position the tile at |x, y|.
77     * @param {number} x The x coordinate, in pixels.
78     * @param {number} y The y coordinate, in pixels.
79     */
80     moveTo: function(x, y) {
81 estade@chromium.org 92404 // left overrides right in LTR, and right takes precedence in RTL.
82 estade@chromium.org 83385 this.style.left = x + 'px';
83 estade@chromium.org 92404 this.style.right = x + 'px';
84 estade@chromium.org 83385 this.style.top = y + 'px';
85     },
86    
87     /**
88     * The handler for dragstart events fired on |this|.
89     * @param {Event} e The event for the drag.
90     * @private
91     */
92     onDragStart_: function(e) {
93 estade@chromium.org 86920 // The user may start dragging again during a previous drag's finishing
94     // animation.
95     if (this.classList.contains('dragging'))
96     this.finalizeDrag_();
97    
98 estade@chromium.org 87727 setCurrentlyDraggingTile(this);
99 estade@chromium.org 83385
100     e.dataTransfer.effectAllowed = 'copyMove';
101 estade@chromium.org 99110 this.firstChild.setDragData(e.dataTransfer);
102 estade@chromium.org 83385
103 estade@chromium.org 84700 // The drag clone is the node we use as a representation during the drag.
104     // It's attached to the top level document element so that it floats above
105     // image masks.
106     this.dragClone = this.cloneNode(true);
107 estade@chromium.org 92404 this.dragClone.style.right = '';
108 estade@chromium.org 84700 this.dragClone.classList.add('drag-representation');
109 estade@chromium.org 91152 $('card-slider-frame').appendChild(this.dragClone);
110 estade@chromium.org 84700 this.eventTracker.add(this.dragClone, 'webkitTransitionEnd',
111     this.onDragCloneTransitionEnd_.bind(this));
112 estade@chromium.org 83385
113     this.classList.add('dragging');
114 estade@chromium.org 92404 // offsetLeft is mirrored in RTL. Un-mirror it.
115     var offsetLeft = ntp4.isRTL() ?
116     this.parentNode.clientWidth - this.offsetLeft :
117     this.offsetLeft;
118     this.dragOffsetX = e.x - offsetLeft - this.parentNode.offsetLeft;
119 estade@chromium.org 89726 this.dragOffsetY = e.y - this.offsetTop -
120 estade@chromium.org 87875 // Unlike offsetTop, this value takes scroll position into account.
121     this.parentNode.getBoundingClientRect().top;
122 estade@chromium.org 84700
123     this.onDragMove_(e);
124 estade@chromium.org 83385 },
125    
126     /**
127     * The handler for drag events fired on |this|.
128     * @param {Event} e The event for the drag.
129     * @private
130     */
131     onDragMove_: function(e) {
132 estade@chromium.org 89726 if (e.view != window || (e.x == 0 && e.y == 0)) {
133 estade@chromium.org 100578 this.dragClone.hidden = true;
134 estade@chromium.org 86366 return;
135     }
136    
137 estade@chromium.org 100578 this.dragClone.hidden = false;
138 estade@chromium.org 89726 this.dragClone.style.left = (e.x - this.dragOffsetX) + 'px';
139     this.dragClone.style.top = (e.y - this.dragOffsetY) + 'px';
140 estade@chromium.org 83385 },
141    
142     /**
143     * The handler for dragend events fired on |this|.
144     * @param {Event} e The event for the drag.
145     * @private
146     */
147     onDragEnd_: function(e) {
148 estade@chromium.org 100578 this.dragClone.hidden = false;
149 estade@chromium.org 86920 this.dragClone.classList.add('placing');
150    
151 estade@chromium.org 87727 setCurrentlyDraggingTile(null);
152 estade@chromium.org 93727
153 estade@chromium.org 95856 // tilePage will be null if we've already been removed.
154 dbeam@chromium.org 101031 var tilePage = this.tilePage;
155     if (tilePage)
156     tilePage.positionTile_(this.index);
157 estade@chromium.org 95856
158     // Take an appropriate action with the drag clone.
159     if (this.landedOnTrash) {
160     this.dragClone.classList.add('deleting');
161 dbeam@chromium.org 101031 } else if (tilePage) {
162     // TODO(dbeam): Until we fix dropEffect to the correct behavior it will
163     // differ on windows - crbug.com/39399. That's why we use the custom
164     // tilePage.lastDropEffect_ instead of e.dataTransfer.dropEffect.
165     if (tilePage.selected && tilePage.lastDropEffect_ != 'copy') {
166 estade@chromium.org 99519 // The drag clone can still be hidden from the last drag move event.
167 estade@chromium.org 100578 this.dragClone.hidden = false;
168 estade@chromium.org 95856 // The tile's contents may have moved following the respositioning;
169     // adjust for that.
170     var contentDiffX = this.dragClone.firstChild.offsetLeft -
171     this.firstChild.offsetLeft;
172     var contentDiffY = this.dragClone.firstChild.offsetTop -
173     this.firstChild.offsetTop;
174     this.dragClone.style.left = (this.gridX + this.parentNode.offsetLeft -
175     contentDiffX) + 'px';
176     this.dragClone.style.top =
177     (this.gridY + this.parentNode.getBoundingClientRect().top -
178     contentDiffY) + 'px';
179 estade@chromium.org 100578 } else if (this.dragClone.hidden) {
180 estade@chromium.org 99519 this.finalizeDrag_();
181 estade@chromium.org 95856 } else {
182     this.dragClone.classList.add('dropped-on-other-page');
183     }
184 estade@chromium.org 93727 }
185    
186 estade@chromium.org 95856 this.landedOnTrash = false;
187 estade@chromium.org 83385 },
188 estade@chromium.org 83887
189     /**
190     * Creates a clone of this node offset by the coordinates. Used for the
191     * dragging effect where a tile appears to float off one side of the grid
192     * and re-appear on the other.
193     * @param {number} x x-axis offset, in pixels.
194     * @param {number} y y-axis offset, in pixels.
195     */
196     showDoppleganger: function(x, y) {
197     // We always have to clear the previous doppleganger to make sure we get
198     // style updates for the contents of this tile.
199     this.clearDoppleganger();
200    
201     var clone = this.cloneNode(true);
202     clone.classList.remove('real');
203     clone.classList.add('doppleganger');
204     var clonelets = clone.querySelectorAll('.real');
205     for (var i = 0; i < clonelets.length; i++) {
206     clonelets[i].classList.remove('real');
207     }
208    
209     this.appendChild(clone);
210     this.doppleganger_ = clone;
211    
212 estade@chromium.org 92404 if (ntp4.isRTL())
213     x *= -1;
214    
215 estade@chromium.org 83887 this.doppleganger_.style.WebkitTransform = 'translate(' + x + 'px, ' +
216     y + 'px)';
217     },
218    
219     /**
220     * Destroys the current doppleganger.
221     */
222     clearDoppleganger: function() {
223     if (this.doppleganger_) {
224     this.removeChild(this.doppleganger_);
225     this.doppleganger_ = null;
226     }
227     },
228    
229     /**
230 estade@chromium.org 84700 * Returns status of doppleganger.
231     * @return {boolean} True if there is a doppleganger showing for |this|.
232     */
233     hasDoppleganger: function() {
234     return !!this.doppleganger_;
235     },
236    
237     /**
238 estade@chromium.org 86920 * Cleans up after the drag is over. This is either called when the
239     * drag representation finishes animating to the final position, or when
240     * the next drag starts (if the user starts a 2nd drag very quickly).
241     * @private
242     */
243     finalizeDrag_: function() {
244     assert(this.classList.contains('dragging'));
245    
246     var clone = this.dragClone;
247     this.dragClone = null;
248    
249     clone.parentNode.removeChild(clone);
250     this.eventTracker.remove(clone, 'webkitTransitionEnd');
251     this.classList.remove('dragging');
252 estade@chromium.org 95856 if (this.firstChild.finalizeDrag)
253     this.firstChild.finalizeDrag();
254 estade@chromium.org 86920 },
255    
256     /**
257 estade@chromium.org 84700 * Called when the drag representation node is done migrating to its final
258     * resting spot.
259 estade@chromium.org 83887 * @param {Event} e The transition end event.
260     */
261 estade@chromium.org 84700 onDragCloneTransitionEnd_: function(e) {
262 estade@chromium.org 86920 if (this.classList.contains('dragging') &&
263 estade@chromium.org 89726 (e.propertyName == 'left' || e.propertyName == 'top' ||
264     e.propertyName == '-webkit-transform')) {
265 estade@chromium.org 86920 this.finalizeDrag_();
266 estade@chromium.org 86366 }
267 estade@chromium.org 86920 },
268 estade@chromium.org 91606
269     /**
270     * Called when an app is removed from Chrome. Animates its disappearance.
271     */
272     doRemove: function() {
273     this.firstChild.classList.add('removing-tile-contents');
274     },
275    
276     /**
277     * Callback for the webkitAnimationEnd event on the tile's contents.
278     * @param {Event} e The event object.
279     */
280     onContentsAnimationEnd_: function(e) {
281     if (this.firstChild.classList.contains('new-tile-contents'))
282     this.firstChild.classList.remove('new-tile-contents');
283 estade@chromium.org 98685 if (this.firstChild.classList.contains('removing-tile-contents'))
284 estade@chromium.org 91735 this.tilePage.removeTile(this);
285 estade@chromium.org 91606 },
286 estade@chromium.org 83385 };
287    
288     /**
289 estade@chromium.org 81845 * Gives the proportion of the row width that is devoted to a single icon.
290     * @param {number} rowTileCount The number of tiles in a row.
291 estade@chromium.org 100105 * @param {number} tileSpacingFraction The proportion of the tile width which
292     * will be used as spacing between tiles.
293 estade@chromium.org 81845 * @return {number} The ratio between icon width and row width.
294     */
295 estade@chromium.org 100105 function tileWidthFraction(rowTileCount, tileSpacingFraction) {
296     return rowTileCount + (rowTileCount - 1) * tileSpacingFraction;
297 estade@chromium.org 81845 }
298    
299     /**
300     * Calculates an assortment of tile-related values for a grid with the
301     * given dimensions.
302     * @param {number} width The pixel width of the grid.
303     * @param {number} numRowTiles The number of tiles in a row.
304 estade@chromium.org 100105 * @param {number} tileSpacingFraction The proportion of the tile width which
305     * will be used as spacing between tiles.
306 estade@chromium.org 81845 * @return {Object} A mapping of pixel values.
307     */
308 estade@chromium.org 100105 function tileValuesForGrid(width, numRowTiles, tileSpacingFraction) {
309     var tileWidth = width / tileWidthFraction(numRowTiles, tileSpacingFraction);
310     var offsetX = tileWidth * (1 + tileSpacingFraction);
311 estade@chromium.org 82146 var interTileSpacing = offsetX - tileWidth;
312 estade@chromium.org 81845
313     return {
314     tileWidth: tileWidth,
315 estade@chromium.org 82146 offsetX: offsetX,
316     interTileSpacing: interTileSpacing,
317 estade@chromium.org 81845 };
318     }
319    
320     // The smallest amount of horizontal blank space to display on the sides when
321 estade@chromium.org 88689 // displaying a wide arrangement. There is an additional 26px of margin from
322     // the tile page padding.
323     var MIN_WIDE_MARGIN = 18;
324 estade@chromium.org 81845
325     /**
326     * Creates a new TilePage object. This object contains tiles and controls
327     * their layout.
328     * @param {Object} gridValues Pixel values that define the size and layout
329     * of the tile grid.
330     * @constructor
331     * @extends {HTMLDivElement}
332     */
333 csilv@chromium.org 91883 function TilePage(gridValues) {
334 estade@chromium.org 81845 var el = cr.doc.createElement('div');
335     el.gridValues_ = gridValues;
336     el.__proto__ = TilePage.prototype;
337     el.initialize();
338    
339     return el;
340     }
341    
342     /**
343     * Takes a collection of grid layout pixel values and updates them with
344     * additional tiling values that are calculated from TilePage constants.
345     * @param {Object} grid The grid layout pixel values to update.
346     */
347     TilePage.initGridValues = function(grid) {
348     // The amount of space we need to display a narrow grid (all narrow grids
349     // are this size).
350     grid.narrowWidth =
351 estade@chromium.org 100105 grid.minTileWidth * tileWidthFraction(grid.minColCount,
352     grid.tileSpacingFraction);
353 estade@chromium.org 81845 // The minimum amount of space we need to display a wide grid.
354     grid.minWideWidth =
355 estade@chromium.org 100105 grid.minTileWidth * tileWidthFraction(grid.maxColCount,
356     grid.tileSpacingFraction);
357 estade@chromium.org 81845 // The largest we will ever display a wide grid.
358     grid.maxWideWidth =
359 estade@chromium.org 100105 grid.maxTileWidth * tileWidthFraction(grid.maxColCount,
360     grid.tileSpacingFraction);
361 estade@chromium.org 81845 // Tile-related pixel values for the narrow display.
362     grid.narrowTileValues = tileValuesForGrid(grid.narrowWidth,
363 estade@chromium.org 100105 grid.minColCount,
364     grid.tileSpacingFraction);
365 estade@chromium.org 81845 // Tile-related pixel values for the minimum narrow display.
366     grid.wideTileValues = tileValuesForGrid(grid.minWideWidth,
367 estade@chromium.org 100105 grid.maxColCount,
368     grid.tileSpacingFraction);
369 estade@chromium.org 86638 };
370 estade@chromium.org 81845
371     TilePage.prototype = {
372     __proto__: HTMLDivElement.prototype,
373    
374     initialize: function() {
375     this.className = 'tile-page';
376    
377 estade@chromium.org 91151 // Div that acts as a custom scrollbar. The scrollbar has to live
378     // outside the content div so it doesn't flicker when scrolling (due to
379     // repainting after the scroll, then repainting again when moved in the
380     // onScroll handler). |scrollbar_| is only aesthetic, and it only
381     // represents the thumb. Actual events are still handled by the invisible
382     // native scrollbars. This div gives us more flexibility with the visuals.
383     this.scrollbar_ = this.ownerDocument.createElement('div');
384     this.scrollbar_.className = 'tile-page-scrollbar';
385     this.scrollbar_.hidden = true;
386     this.appendChild(this.scrollbar_);
387    
388     // This contains everything but the scrollbar.
389     this.content_ = this.ownerDocument.createElement('div');
390     this.content_.className = 'tile-page-content';
391     this.appendChild(this.content_);
392    
393 estade@chromium.org 85085 // Div that sets the vertical position of the tile grid.
394     this.topMargin_ = this.ownerDocument.createElement('div');
395     this.topMargin_.className = 'top-margin';
396 estade@chromium.org 91151 this.content_.appendChild(this.topMargin_);
397 estade@chromium.org 85085
398 estade@chromium.org 81845 // Div that holds the tiles.
399     this.tileGrid_ = this.ownerDocument.createElement('div');
400     this.tileGrid_.className = 'tile-grid';
401 estade@chromium.org 100105 this.tileGrid_.style.minWidth = this.gridValues_.narrowWidth + 'px';
402 estade@chromium.org 91151 this.content_.appendChild(this.tileGrid_);
403 estade@chromium.org 81845
404     // Ordered list of our tiles.
405 estade@chromium.org 83887 this.tileElements_ = this.tileGrid_.getElementsByClassName('tile real');
406 estade@chromium.org 81845
407 estade@chromium.org 85085 // These are properties used in updateTopMargin.
408     this.animatedTopMarginPx_ = 0;
409     this.topMarginPx_ = 0;
410 estade@chromium.org 81845
411     this.eventTracker = new EventTracker();
412     this.eventTracker.add(window, 'resize', this.onResize_.bind(this));
413 estade@chromium.org 83385
414 estade@chromium.org 87727 this.addEventListener('DOMNodeInsertedIntoDocument',
415 estade@chromium.org 89726 this.onNodeInsertedIntoDocument_);
416 estade@chromium.org 97929
417     this.addEventListener('mousewheel', this.onMouseWheel_);
418 estade@chromium.org 91151 this.content_.addEventListener('scroll', this.onScroll_.bind(this));
419 estade@chromium.org 87727
420 estade@chromium.org 93727 this.dragWrapper_ = new DragWrapper(this.tileGrid_, this);
421 estade@chromium.org 94375
422     $('page-list').addEventListener(
423     CardSlider.EventType.CARD_CHANGED,
424     this.onCardChanged.bind(this));
425 estade@chromium.org 81845 },
426    
427 csilv@chromium.org 98657 get tiles() {
428     return this.tileElements_;
429     },
430    
431 estade@chromium.org 87727 get tileCount() {
432     return this.tileElements_.length;
433     },
434    
435 estade@chromium.org 89726 get selected() {
436     return Array.prototype.indexOf.call(this.parentNode.children, this) ==
437     ntp4.getCardSlider().currentCard;
438     },
439    
440 estade@chromium.org 81845 /**
441 estade@chromium.org 90992 * The size of the margin (unused space) on the sides of the tile grid, in
442     * pixels.
443     * @type {number}
444     */
445     get sideMargin() {
446     return this.layoutValues_.leftMargin;
447     },
448    
449     /**
450 estade@chromium.org 91151 * Returns the width of the scrollbar, in pixels, if it is active, or 0
451     * otherwise.
452     * @type {number}
453     */
454     get scrollbarWidth() {
455     return this.scrollbar_.hidden ? 0 : 13;
456     },
457    
458     /**
459 csilv@chromium.org 99700 * Returns any extra padding to insert to the bottom of a tile page. By
460     * default there is none, but subclasses can override.
461     * @type {number}
462     */
463     get extraBottomPadding() {
464     return 0;
465     },
466    
467     /**
468 estade@chromium.org 81845 * Cleans up resources that are no longer needed after this TilePage
469     * instance is removed from the DOM.
470     */
471     tearDown: function() {
472     this.eventTracker.removeAll();
473     },
474    
475     /**
476 estade@chromium.org 91606 * Appends a tile to the end of the tile grid.
477     * @param {HTMLElement} tileElement The contents of the tile.
478     * @param {?boolean} animate If true, the append will be animated.
479 estade@chromium.org 81845 * @protected
480     */
481 estade@chromium.org 91606 appendTile: function(tileElement, animate) {
482     this.addTileAt(tileElement, this.tileElements_.length, animate);
483 estade@chromium.org 86184 },
484 estade@chromium.org 85085
485 estade@chromium.org 86184 /**
486     * Adds the given element to the tile grid.
487     * @param {Node} tileElement The tile object/node to insert.
488     * @param {number} index The location in the tile grid to insert it at.
489 estade@chromium.org 91735 * @param {?boolean} animate If true, the tile in question will be animated
490     * (other tiles, if they must reposition, do not animate).
491 estade@chromium.org 86184 * @protected
492     */
493 estade@chromium.org 91606 addTileAt: function(tileElement, index, animate) {
494     this.classList.remove('animating-tile-page');
495     if (animate)
496     tileElement.classList.add('new-tile-contents');
497 estade@chromium.org 83385 var wrapperDiv = new Tile(tileElement);
498 estade@chromium.org 86184 if (index == this.tileElements_.length) {
499     this.tileGrid_.appendChild(wrapperDiv);
500     } else {
501     this.tileGrid_.insertBefore(wrapperDiv,
502     this.tileElements_[index]);
503     }
504     this.calculateLayoutValues_();
505 csilv@chromium.org 99700 this.heightChanged_();
506 estade@chromium.org 81845
507 estade@chromium.org 86184 this.positionTile_(index);
508 estade@chromium.org 81845 },
509    
510     /**
511 estade@chromium.org 91735 * Removes the given tile and animates the respositioning of the other
512     * tiles.
513     * @param {HTMLElement} tile The tile to remove from |tileGrid_|.
514 csilv@chromium.org 98657 * @param {?boolean} animate If true, tiles will animate.
515 estade@chromium.org 91735 */
516 csilv@chromium.org 98657 removeTile: function(tile, animate) {
517     if (animate)
518     this.classList.add('animating-tile-page');
519 dbeam@chromium.org 99701 var index = tile.index;
520 estade@chromium.org 91735 tile.parentNode.removeChild(tile);
521     this.calculateLayoutValues_();
522     for (var i = index; i < this.tileElements_.length; i++) {
523     this.positionTile_(i);
524     }
525     },
526    
527     /**
528 csilv@chromium.org 96867 * Removes all tiles from the page.
529     */
530     removeAllTiles: function() {
531     this.tileGrid_.innerHTML = '';
532     },
533    
534     /**
535 estade@chromium.org 85085 * Makes some calculations for tile layout. These change depending on
536     * height, width, and the number of tiles.
537 estade@chromium.org 94375 * TODO(estade): optimize calls to this function. Do nothing if the page is
538     * hidden, but call before being shown.
539 estade@chromium.org 81845 * @private
540     */
541 estade@chromium.org 83385 calculateLayoutValues_: function() {
542 estade@chromium.org 81845 var grid = this.gridValues_;
543     var availableSpace = this.tileGrid_.clientWidth - 2 * MIN_WIDE_MARGIN;
544     var wide = availableSpace >= grid.minWideWidth;
545     var numRowTiles = wide ? grid.maxColCount : grid.minColCount;
546    
547     var effectiveGridWidth = wide ?
548     Math.min(Math.max(availableSpace, grid.minWideWidth),
549     grid.maxWideWidth) :
550     grid.narrowWidth;
551 estade@chromium.org 100105 var realTileValues = tileValuesForGrid(effectiveGridWidth, numRowTiles,
552     grid.tileSpacingFraction);
553 estade@chromium.org 83385
554 estade@chromium.org 81845 // leftMargin centers the grid within the avaiable space.
555     var minMargin = wide ? MIN_WIDE_MARGIN : 0;
556     var leftMargin =
557     Math.max(minMargin,
558     (this.tileGrid_.clientWidth - effectiveGridWidth) / 2);
559 estade@chromium.org 85085
560     var rowHeight = this.heightForWidth(realTileValues.tileWidth) +
561     realTileValues.interTileSpacing;
562    
563     this.layoutValues_ = {
564 estade@chromium.org 83385 numRowTiles: numRowTiles,
565     leftMargin: leftMargin,
566     colWidth: realTileValues.offsetX,
567 estade@chromium.org 85085 rowHeight: rowHeight,
568 estade@chromium.org 83385 tileWidth: realTileValues.tileWidth,
569     wide: wide,
570     };
571 estade@chromium.org 85085
572     // We need to update the top margin as well.
573     this.updateTopMargin_();
574 estade@chromium.org 97929
575     this.firePageLayoutEvent_();
576 estade@chromium.org 83385 },
577 estade@chromium.org 81845
578 estade@chromium.org 83385 /**
579 estade@chromium.org 97929 * Dispatches the custom pagelayout event.
580     * @private
581 estade@chromium.org 90992 */
582 estade@chromium.org 97929 firePageLayoutEvent_: function() {
583     cr.dispatchSimpleEvent(this, 'pagelayout', true, true);
584 estade@chromium.org 90992 },
585    
586     /**
587 estade@chromium.org 83385 * Calculates the x/y coordinates for an element and moves it there.
588     * @param {number} index The index of the element to be positioned.
589     * @param {number} indexOffset If provided, this is added to |index| when
590     * positioning the tile. The effect is that the tile will be positioned
591     * in a non-default location.
592     * @private
593     */
594     positionTile_: function(index, indexOffset) {
595     var grid = this.gridValues_;
596 estade@chromium.org 85085 var layout = this.layoutValues_;
597 estade@chromium.org 83385
598     indexOffset = typeof indexOffset != 'undefined' ? indexOffset : 0;
599 estade@chromium.org 83887 // Add the offset _after_ the modulus division. We might want to show the
600     // tile off the side of the grid.
601     var col = index % layout.numRowTiles + indexOffset;
602     var row = Math.floor(index / layout.numRowTiles);
603 estade@chromium.org 83385 // Calculate the final on-screen position for the tile.
604     var realX = col * layout.colWidth + layout.leftMargin;
605     var realY = row * layout.rowHeight;
606    
607     // Calculate the portion of the tile's position that should be animated.
608     var animatedTileValues = layout.wide ?
609     grid.wideTileValues : grid.narrowTileValues;
610     // Animate the difference between three-wide and six-wide.
611     var animatedLeftMargin = layout.wide ?
612     0 : (grid.minWideWidth - MIN_WIDE_MARGIN - grid.narrowWidth) / 2;
613     var animatedX = col * animatedTileValues.offsetX + animatedLeftMargin;
614     var animatedY = row * (this.heightForWidth(animatedTileValues.tileWidth) +
615     animatedTileValues.interTileSpacing);
616    
617     var tile = this.tileElements_[index];
618     tile.setGridPosition(animatedX, animatedY);
619     tile.firstChild.setBounds(layout.tileWidth,
620     realX - animatedX,
621     realY - animatedY);
622 estade@chromium.org 83887
623     // This code calculates whether the tile needs to show a clone of itself
624     // wrapped around the other side of the tile grid.
625 estade@chromium.org 84700 var offTheRight = col == layout.numRowTiles ||
626     (col == layout.numRowTiles - 1 && tile.hasDoppleganger());
627     var offTheLeft = col == -1 || (col == 0 && tile.hasDoppleganger());
628 estade@chromium.org 93727 if (this.isCurrentDragTarget && (offTheRight || offTheLeft)) {
629 estade@chromium.org 83887 var sign = offTheRight ? 1 : -1;
630     tile.showDoppleganger(-layout.numRowTiles * layout.colWidth * sign,
631     layout.rowHeight * sign);
632     } else {
633     tile.clearDoppleganger();
634     }
635 estade@chromium.org 87875
636 estade@chromium.org 91151 if (index == this.tileElements_.length - 1) {
637 estade@chromium.org 87875 this.tileGrid_.style.height = (realY + layout.rowHeight) + 'px';
638 estade@chromium.org 91151 this.queueUpdateScrollbars_();
639     }
640 estade@chromium.org 81845 },
641    
642     /**
643 estade@chromium.org 83385 * Gets the index of the tile that should occupy coordinate (x, y). Note
644     * that this function doesn't care where the tiles actually are, and will
645     * return an index even for the space between two tiles. This function is
646     * effectively the inverse of |positionTile_|.
647 estade@chromium.org 96628 * @param {number} x The x coordinate, in pixels, relative to the left of
648     * |this|.
649     * @param {number} y The y coordinate, in pixels, relative to the top of
650     * |this|.
651 estade@chromium.org 83385 * @private
652     */
653     getWouldBeIndexForPoint_: function(x, y) {
654     var grid = this.gridValues_;
655 estade@chromium.org 85085 var layout = this.layoutValues_;
656 estade@chromium.org 83385
657 estade@chromium.org 96628 var gridClientRect = this.tileGrid_.getBoundingClientRect();
658     var col = Math.floor((x - gridClientRect.left - layout.leftMargin) /
659     layout.colWidth);
660 estade@chromium.org 83887 if (col < 0 || col >= layout.numRowTiles)
661     return -1;
662    
663 estade@chromium.org 92404 if (ntp4.isRTL())
664     col = layout.numRowTiles - 1 - col;
665    
666 estade@chromium.org 96628 var row = Math.floor((y - gridClientRect.top) / layout.rowHeight);
667 estade@chromium.org 83385 return row * layout.numRowTiles + col;
668     },
669    
670     /**
671 estade@chromium.org 81845 * Window resize event handler. Window resizes may trigger re-layouts.
672     * @param {Object} e The resize event.
673     */
674     onResize_: function(e) {
675 estade@chromium.org 85085 if (this.lastWidth_ == this.clientWidth &&
676     this.lastHeight_ == this.clientHeight) {
677 estade@chromium.org 81845 return;
678 estade@chromium.org 85085 }
679 estade@chromium.org 81845
680 estade@chromium.org 85085 this.calculateLayoutValues_();
681    
682     this.lastWidth_ = this.clientWidth;
683     this.lastHeight_ = this.clientHeight;
684 estade@chromium.org 83385 this.classList.add('animating-tile-page');
685 estade@chromium.org 89726 this.heightChanged_();
686 estade@chromium.org 81845
687 dbeam@chromium.org 99701 this.repositionTiles_();
688 estade@chromium.org 81845 },
689 estade@chromium.org 82146
690     /**
691 estade@chromium.org 85085 * The tile grid has an image mask which fades at the edges. We only show
692     * the mask when there is an active drag; it obscures doppleganger tiles
693     * as they enter or exit the grid.
694     * @private
695 estade@chromium.org 83887 */
696     updateMask_: function() {
697 estade@chromium.org 93727 if (!this.isCurrentDragTarget) {
698 estade@chromium.org 87875 this.tileGrid_.style.WebkitMaskBoxImage = '';
699 estade@chromium.org 83887 return;
700 estade@chromium.org 85085 }
701 estade@chromium.org 83887
702 estade@chromium.org 85085 var leftMargin = this.layoutValues_.leftMargin;
703 estade@chromium.org 88689 var fadeDistance = Math.min(leftMargin, 20);
704 estade@chromium.org 83887 var gradient =
705     '-webkit-linear-gradient(left,' +
706     'transparent, ' +
707     'transparent ' + (leftMargin - fadeDistance) + 'px, ' +
708     'black ' + leftMargin + 'px, ' +
709 estade@chromium.org 88689 'black ' + (this.tileGrid_.clientWidth - leftMargin) + 'px, ' +
710     'transparent ' + (this.tileGrid_.clientWidth - leftMargin +
711     fadeDistance) + 'px, ' +
712 estade@chromium.org 83887 'transparent)';
713 estade@chromium.org 87875 this.tileGrid_.style.WebkitMaskBoxImage = gradient;
714 estade@chromium.org 83887 },
715    
716 estade@chromium.org 85085 updateTopMargin_: function() {
717     var layout = this.layoutValues_;
718    
719     // The top margin is set so that the vertical midpoint of the grid will
720     // be 1/3 down the page.
721 estade@chromium.org 87727 var numTiles = this.tileCount +
722 estade@chromium.org 93727 (this.isCurrentDragTarget && !this.withinPageDrag_ ? 1 : 0);
723 estade@chromium.org 87727 var numRows = Math.ceil(numTiles / layout.numRowTiles);
724 estade@chromium.org 85085 var usedHeight = layout.rowHeight * numRows;
725     // 100 matches the top padding of tile-page.
726     var newMargin = document.documentElement.clientHeight / 3 -
727 csilv@chromium.org 99485 usedHeight / 2 - 100 - this.content_.offsetTop;
728 estade@chromium.org 85085 newMargin = Math.max(newMargin, 0);
729    
730     // |newMargin| is the final margin we actually want to show. However,
731     // part of that should be animated and part should not (for the same
732     // reason as with leftMargin). The approach is to consider differences
733     // when the layout changes from wide to narrow or vice versa as
734     // 'animatable'. These differences accumulate in animatedTopMarginPx_,
735     // while topMarginPx_ caches the real (total) margin. Either of these
736     // calculations may come out to be negative, so we use margins as the
737     // css property.
738    
739     if (typeof this.topMarginIsForWide_ == 'undefined')
740     this.topMarginIsForWide_ = layout.wide;
741     if (this.topMarginIsForWide_ != layout.wide) {
742     this.animatedTopMarginPx_ += newMargin - this.topMarginPx_;
743     this.topMargin_.style.marginBottom =
744     this.animatedTopMarginPx_ + 'px';
745     }
746    
747     this.topMarginIsForWide_ = layout.wide;
748     this.topMarginPx_ = newMargin;
749     this.topMargin_.style.marginTop =
750     (this.topMarginPx_ - this.animatedTopMarginPx_) + 'px';
751     },
752    
753 estade@chromium.org 83887 /**
754 estade@chromium.org 89726 * Handles final setup that can only happen after |this| is inserted into
755     * the page.
756 estade@chromium.org 97929 * @private
757 estade@chromium.org 89726 */
758     onNodeInsertedIntoDocument_: function(e) {
759     this.calculateLayoutValues_();
760     this.heightChanged_();
761     },
762    
763     /**
764     * Called when the height of |this| has changed: update the size of
765     * tileGrid.
766 estade@chromium.org 97929 * @private
767 estade@chromium.org 89726 */
768     heightChanged_: function() {
769     // The tile grid will expand to the bottom footer, or enough to hold all
770     // the tiles, whichever is greater. It would be nicer if tilePage were
771     // a flex box, and the tile grid could be box-flex: 1, but this exposes a
772     // bug where repositioning tiles will cause the scroll position to reset.
773     this.tileGrid_.style.minHeight = (this.clientHeight -
774 csilv@chromium.org 99700 this.tileGrid_.offsetTop - this.content_.offsetTop -
775     this.extraBottomPadding) + 'px';
776 estade@chromium.org 89726 },
777    
778     /**
779 estade@chromium.org 97929 * Scrolls the page in response to a mousewheel event.
780     * @param {Event} e The mousewheel event.
781 estade@chromium.org 91151 */
782 estade@chromium.org 97929 handleMouseWheel: function(e) {
783     this.content_.scrollTop -= e.wheelDeltaY / 3;
784 estade@chromium.org 91151 },
785    
786     /**
787 estade@chromium.org 97929 * Handles mouse wheels on |this|. We handle this explicitly because we want
788     * a consistent experience whether the user scrolls on the page or on the
789     * page switcher (handleMouseWheel provides a common conversion factor
790     * between wheel delta and scroll delta).
791     * @param {Event} e The mousewheel event.
792     * @private
793     */
794     onMouseWheel_: function(e) {
795     if (e.wheelDeltaY == 0)
796     return;
797    
798     this.handleMouseWheel(e);
799     e.preventDefault();
800     e.stopPropagation();
801     },
802    
803     /**
804 estade@chromium.org 91151 * Handler for the 'scroll' event on |content_|.
805     * @param {Event} e The scroll event.
806 estade@chromium.org 97929 * @private
807 estade@chromium.org 91151 */
808     onScroll_: function(e) {
809     this.queueUpdateScrollbars_();
810     },
811    
812     /**
813     * ID of scrollbar update timer. If 0, there's no scrollbar re-calc queued.
814 estade@chromium.org 97929 * @private
815 estade@chromium.org 91151 */
816     scrollbarUpdate_: 0,
817    
818     /**
819     * Queues an update on the custom scrollbar. Used for two reasons: first,
820     * coalescing of multiple updates, and second, because action like
821     * repositioning a tile can require a delay before they affect values
822     * like clientHeight.
823 estade@chromium.org 97929 * @private
824 estade@chromium.org 91151 */
825     queueUpdateScrollbars_: function() {
826     if (this.scrollbarUpdate_)
827     return;
828    
829     this.scrollbarUpdate_ = window.setTimeout(
830     this.doUpdateScrollbars_.bind(this), 0);
831     },
832    
833     /**
834     * Does the work of calculating the visibility, height and position of the
835     * scrollbar thumb (there is no track or buttons).
836 estade@chromium.org 97929 * @private
837 estade@chromium.org 91151 */
838     doUpdateScrollbars_: function() {
839     this.scrollbarUpdate_ = 0;
840    
841     var content = this.content_;
842 csilv@chromium.org 96895
843 csilv@chromium.org 99485 // Adjust scroll-height to account for possible header-bar.
844     var adjustedScrollHeight = content.scrollHeight - content.offsetTop;
845 csilv@chromium.org 96895
846 csilv@chromium.org 99485 if (adjustedScrollHeight <= content.clientHeight) {
847 estade@chromium.org 91151 this.scrollbar_.hidden = true;
848     return;
849     } else {
850     this.scrollbar_.hidden = false;
851     }
852    
853 csilv@chromium.org 96895 var thumbTop = content.offsetTop +
854 csilv@chromium.org 99485 content.scrollTop / adjustedScrollHeight * content.clientHeight;
855     var thumbHeight = content.clientHeight / adjustedScrollHeight *
856 estade@chromium.org 91151 this.clientHeight;
857    
858     this.scrollbar_.style.top = thumbTop + 'px';
859     this.scrollbar_.style.height = thumbHeight + 'px';
860 estade@chromium.org 97929 this.firePageLayoutEvent_();
861 estade@chromium.org 91151 },
862    
863     /**
864 estade@chromium.org 82146 * Get the height for a tile of a certain width. Override this function to
865     * get non-square tiles.
866     * @param {number} width The pixel width of a tile.
867     * @return {number} The height for |width|.
868     */
869     heightForWidth: function(width) {
870     return width;
871     },
872 estade@chromium.org 83385
873 estade@chromium.org 94375 /**
874     * Handle for CARD_CHANGED.
875     * @param {Event} e The card slider event.
876     */
877     onCardChanged: function(e) {
878     // When we are selected, we re-calculate the layout values. (See comment
879     // in doDrop.)
880     if (e.cardSlider.currentCardValue == this)
881     this.calculateLayoutValues_();
882     },
883    
884 estade@chromium.org 83385 /** Dragging **/
885    
886 estade@chromium.org 93727 get isCurrentDragTarget() {
887     return this.dragWrapper_.isCurrentDragTarget;
888 estade@chromium.org 86184 },
889 estade@chromium.org 83385
890 estade@chromium.org 86184 /**
891     * Thunk for dragleave events fired on |tileGrid_|.
892     * @param {Event} e A MouseEvent for the drag.
893     */
894 estade@chromium.org 93727 doDragLeave: function(e) {
895     this.cleanupDrag();
896 estade@chromium.org 86184 },
897    
898     /**
899     * Performs all actions necessary when a drag enters the tile page.
900     * @param {Event} e A mouseover event for the drag enter.
901     */
902 estade@chromium.org 93727 doDragEnter: function(e) {
903 estade@chromium.org 86184
904 estade@chromium.org 85085 // Applies the mask so doppleganger tiles disappear into the fog.
905     this.updateMask_();
906    
907 estade@chromium.org 83385 this.classList.add('animating-tile-page');
908 estade@chromium.org 86638 this.withinPageDrag_ = this.contains(currentlyDraggingTile);
909 estade@chromium.org 84700 this.dragItemIndex_ = this.withinPageDrag_ ?
910 estade@chromium.org 86638 currentlyDraggingTile.index : this.tileElements_.length;
911 estade@chromium.org 83385 this.currentDropIndex_ = this.dragItemIndex_;
912 estade@chromium.org 87727
913     // The new tile may change the number of rows, hence the top margin
914     // will change.
915     if (!this.withinPageDrag_)
916     this.updateTopMargin_();
917 estade@chromium.org 89726
918 estade@chromium.org 93727 this.doDragOver(e);
919 estade@chromium.org 83385 },
920    
921     /**
922 estade@chromium.org 86184 * Performs all actions necessary when the user moves the cursor during
923     * a drag over the tile page.
924     * @param {Event} e A mouseover event for the drag over.
925 estade@chromium.org 83385 */
926 estade@chromium.org 93727 doDragOver: function(e) {
927 estade@chromium.org 83385 e.preventDefault();
928    
929 estade@chromium.org 98342 this.setDropEffect(e.dataTransfer);
930 estade@chromium.org 87875 var newDragIndex = this.getWouldBeIndexForPoint_(e.pageX, e.pageY);
931 estade@chromium.org 83385 if (newDragIndex < 0 || newDragIndex >= this.tileElements_.length)
932     newDragIndex = this.dragItemIndex_;
933     this.updateDropIndicator_(newDragIndex);
934 dbeam@chromium.org 101031
935     this.lastDropEffect_ = e.dataTransfer.dropEffect;
936 estade@chromium.org 83385 },
937    
938     /**
939 estade@chromium.org 86184 * Performs all actions necessary when the user completes a drop.
940     * @param {Event} e A mouseover event for the drag drop.
941 estade@chromium.org 83385 */
942 estade@chromium.org 93727 doDrop: function(e) {
943 estade@chromium.org 83887 e.stopPropagation();
944    
945 estade@chromium.org 83385 var index = this.currentDropIndex_;
946 estade@chromium.org 86373 // Only change data if this was not a 'null drag'.
947     if (!((index == this.dragItemIndex_) && this.withinPageDrag_)) {
948     var adjustedIndex = this.currentDropIndex_ +
949     (index > this.dragItemIndex_ ? 1 : 0);
950 estade@chromium.org 95856 if (this.withinPageDrag_) {
951 estade@chromium.org 86373 this.tileGrid_.insertBefore(
952 estade@chromium.org 86638 currentlyDraggingTile,
953 estade@chromium.org 86373 this.tileElements_[adjustedIndex]);
954 dbeam@chromium.org 99701 this.tileMoved(currentlyDraggingTile, this.dragItemIndex_);
955 estade@chromium.org 86373 } else {
956 estade@chromium.org 97929 var originalPage = currentlyDraggingTile ?
957     currentlyDraggingTile.tilePage : null;
958 estade@chromium.org 95856 this.addDragData(e.dataTransfer, adjustedIndex);
959 estade@chromium.org 97929 if (originalPage)
960     originalPage.cleanupDrag();
961 estade@chromium.org 86373 }
962 estade@chromium.org 95856
963     // Dropping the icon may cause topMargin to change, but changing it
964     // now would cause everything to move (annoying), so we leave it
965     // alone. The top margin will be re-calculated next time the window is
966     // resized or the page is selected.
967 estade@chromium.org 86184 }
968 estade@chromium.org 83385
969 estade@chromium.org 86184 this.classList.remove('animating-tile-page');
970 estade@chromium.org 93727 this.cleanupDrag();
971 estade@chromium.org 83385 },
972    
973     /**
974 estade@chromium.org 93262 * Appends the currently dragged tile to the end of the page. Called
975     * from outside the page, e.g. when dropping on a nav dot.
976     */
977     appendDraggingTile: function() {
978     var originalPage = currentlyDraggingTile.tilePage;
979     if (originalPage == this)
980     return;
981    
982 estade@chromium.org 96688 this.addDragData(null, this.tileElements_.length);
983 estade@chromium.org 93727 originalPage.cleanupDrag();
984 estade@chromium.org 93262 },
985    
986     /**
987 estade@chromium.org 83887 * Makes sure all the tiles are in the right place after a drag is over.
988     */
989 estade@chromium.org 93727 cleanupDrag: function() {
990 dbeam@chromium.org 99701 this.repositionTiles_(currentlyDraggingTile);
991 estade@chromium.org 85085 // Remove the drag mask.
992     this.updateMask_();
993 estade@chromium.org 83887 },
994    
995     /**
996 dbeam@chromium.org 99701 * Reposition all the tiles (possibly ignoring one).
997     * @param {Node} ignoreNode An optional node to ignore.
998     * @private
999     */
1000     repositionTiles_: function(ignoreNode) {
1001     for (var i = 0; i < this.tileElements_.length; i++) {
1002     if (!ignoreNode || ignoreNode !== this.tileElements_[i])
1003     this.positionTile_(i);
1004     }
1005     },
1006    
1007     /**
1008 estade@chromium.org 83385 * Updates the visual indicator for the drop location for the active drag.
1009     * @param {Event} e A MouseEvent for the drag.
1010     * @private
1011     */
1012     updateDropIndicator_: function(newDragIndex) {
1013     var oldDragIndex = this.currentDropIndex_;
1014     if (newDragIndex == oldDragIndex)
1015     return;
1016    
1017     var repositionStart = Math.min(newDragIndex, oldDragIndex);
1018     var repositionEnd = Math.max(newDragIndex, oldDragIndex);
1019    
1020     for (var i = repositionStart; i <= repositionEnd; i++) {
1021     if (i == this.dragItemIndex_)
1022     continue;
1023     else if (i > this.dragItemIndex_)
1024     var adjustment = i <= newDragIndex ? -1 : 0;
1025     else
1026     var adjustment = i >= newDragIndex ? 1 : 0;
1027    
1028     this.positionTile_(i, adjustment);
1029     }
1030     this.currentDropIndex_ = newDragIndex;
1031     },
1032 estade@chromium.org 84700
1033     /**
1034 estade@chromium.org 86184 * Checks if a page can accept a drag with the given data.
1035 estade@chromium.org 93727 * @param {Event} e The drag event if the drag object. Implementations will
1036     * likely want to check |e.dataTransfer|.
1037 estade@chromium.org 86184 * @return {boolean} True if this page can handle the drag.
1038     */
1039 estade@chromium.org 93727 shouldAcceptDrag: function(e) {
1040 estade@chromium.org 86184 return false;
1041     },
1042    
1043     /**
1044 estade@chromium.org 95856 * Called to accept a drag drop. Will not be called for in-page drops.
1045 estade@chromium.org 86184 * @param {Object} dataTransfer The data transfer object that holds the drop
1046 estade@chromium.org 95856 * data. This should only be used if currentlyDraggingTile is null.
1047 estade@chromium.org 86184 * @param {number} index The tile index at which the drop occurred.
1048     */
1049 estade@chromium.org 95856 addDragData: function(dataTransfer, index) {
1050 estade@chromium.org 86184 assert(false);
1051     },
1052 estade@chromium.org 87423
1053     /**
1054     * Called when a tile has been moved (via dragging). Override this to make
1055     * backend updates.
1056     * @param {Node} draggedTile The tile that was dropped.
1057 dbeam@chromium.org 99701 * @param {number} prevIndex The previous index of the tile.
1058 estade@chromium.org 87423 */
1059 dbeam@chromium.org 99701 tileMoved: function(draggedTile, prevIndex) {
1060 estade@chromium.org 87423 },
1061 estade@chromium.org 98342
1062     /**
1063     * Sets the drop effect on |dataTransfer| to the desired value (e.g.
1064     * 'copy').
1065     * @param {Object} dataTransfer The drag event dataTransfer object.
1066     */
1067     setDropEffect: function(dataTransfer) {
1068     assert(false);
1069     },
1070 estade@chromium.org 81845 };
1071    
1072     return {
1073 estade@chromium.org 86638 getCurrentlyDraggingTile: getCurrentlyDraggingTile,
1074 estade@chromium.org 81845 TilePage: TilePage,
1075     };
1076     });

Properties

Name Value
svn:eol-style LF

Powered by ViewVC 1.1.26 ViewVC Help