OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 cr.define('ntp', function() { | 5 cr.define('ntp', function() { |
6 'use strict'; | 6 'use strict'; |
7 | 7 |
| 8 |
| 9 /** |
| 10 * A virtual Tile class. Each TilePage subclass should have its own Tile |
| 11 * subclass implemented too (e.g. MostVisitedPage contains MostVisited |
| 12 * tiles, and MostVisited is a Tile subclass). |
| 13 * @constructor |
| 14 * @param {Object} gridValues Tile page configuration object. |
| 15 */ |
| 16 function Tile(gridValues) { |
| 17 throw 'Tile is a virtual class and is not supposed to be instantiated'; |
| 18 } |
| 19 |
| 20 /** |
| 21 * Creates a Tile subclass. |
| 22 */ |
| 23 Tile.subclass = function(Subclass) { |
| 24 var Base = Tile.prototype; |
| 25 for (var name in Base) { |
| 26 if (!Subclass.hasOwnProperty(name)) { |
| 27 Subclass[name] = Base[name]; |
| 28 } |
| 29 } |
| 30 for (var name in TileGetters) { |
| 31 if (!Subclass.hasOwnProperty(name)) { |
| 32 Subclass.__defineGetter__(name, TileGetters[name]); |
| 33 } |
| 34 } |
| 35 return Subclass; |
| 36 }; |
| 37 |
| 38 Tile.prototype = { |
| 39 initialize: function(gridValues) { |
| 40 // TODO: rename to tile |
| 41 this.baseClassName = 'tile ' + gridValues.tileClassName; |
| 42 this.className = this.baseClassName; |
| 43 |
| 44 // TODO set using CSS? |
| 45 if (gridValues.reinforceStyles) { |
| 46 var style = this.style; |
| 47 style.width = gridValues.tileWidth + 'px'; |
| 48 style.height = gridValues.tileHeight + 'px'; |
| 49 style.borderWidth = gridValues.tileBorderWidth + 'px'; |
| 50 } |
| 51 }, |
| 52 |
| 53 tearDown_: function() { |
| 54 }, |
| 55 }; |
| 56 |
| 57 var TileGetters = { |
| 58 'tileCell': function() { |
| 59 return findAncestorByClass(this, 'tile-cell'); |
| 60 }, |
| 61 'index': function() { |
| 62 assert(this.tileCell); |
| 63 return this.tileCell.index; |
| 64 } |
| 65 }; |
| 66 |
| 67 |
| 68 |
8 // We can't pass the currently dragging tile via dataTransfer because of | 69 // We can't pass the currently dragging tile via dataTransfer because of |
9 // http://crbug.com/31037 | 70 // http://crbug.com/31037 |
| 71 // TODO(xci) drag |
10 var currentlyDraggingTile = null; | 72 var currentlyDraggingTile = null; |
11 function getCurrentlyDraggingTile() { | 73 function getCurrentlyDraggingTile() { |
12 return currentlyDraggingTile; | 74 return currentlyDraggingTile; |
13 } | 75 } |
14 function setCurrentlyDraggingTile(tile) { | 76 function setCurrentlyDraggingTile(tile) { |
15 currentlyDraggingTile = tile; | 77 currentlyDraggingTile = tile; |
16 if (tile) | 78 if (tile) |
17 ntp.enterRearrangeMode(); | 79 ntp.enterRearrangeMode(); |
18 else | 80 else |
19 ntp.leaveRearrangeMode(); | 81 ntp.leaveRearrangeMode(); |
20 } | 82 } |
21 | 83 |
22 /** | 84 /** |
23 * Changes the current dropEffect of a drag. This modifies the native cursor | 85 * Changes the current dropEffect of a drag. This modifies the native cursor |
24 * and serves as an indicator of what we should do at the end of the drag as | 86 * and serves as an indicator of what we should do at the end of the drag as |
25 * well as give indication to the user if a drop would succeed if they let go. | 87 * well as give indication to the user if a drop would succeed if they let go. |
26 * @param {DataTransfer} dataTransfer A dataTransfer object from a drag event. | 88 * @param {DataTransfer} dataTransfer A dataTransfer object from a drag event. |
27 * @param {string} effect A drop effect to change to (i.e. copy, move, none). | 89 * @param {string} effect A drop effect to change to (i.e. copy, move, none). |
28 */ | 90 */ |
| 91 // TODO(xci) drag |
29 function setCurrentDropEffect(dataTransfer, effect) { | 92 function setCurrentDropEffect(dataTransfer, effect) { |
30 dataTransfer.dropEffect = effect; | 93 dataTransfer.dropEffect = effect; |
31 if (currentlyDraggingTile) | 94 if (currentlyDraggingTile) |
32 currentlyDraggingTile.lastDropEffect = dataTransfer.dropEffect; | 95 currentlyDraggingTile.lastDropEffect = dataTransfer.dropEffect; |
33 } | 96 } |
34 | 97 |
35 /** | 98 /** |
36 * Creates a new Tile object. Tiles wrap content on a TilePage, providing | 99 * Creates a new TileCell object. A TileCell represents a cell in the |
37 * some styling and drag functionality. | 100 * TilePage's grid. A TilePage uses TileCells to position Tiles in the proper |
| 101 * place and to animate them individually. Each TileCell is associated to |
| 102 * one Tile at a time (or none if it is a filler object), and that association |
| 103 * might change when the grid is resized. When that happens, the grid is |
| 104 * updated and the Tiles are moved to the proper TileCell. We cannot move the |
| 105 * the TileCell itself during the resize because this transition is animated |
| 106 * with CSS and there's no way to stop CSS animations, and we really want to |
| 107 * animate with CSS to take advantage of hardware acceleration. |
38 * @constructor | 108 * @constructor |
39 * @extends {HTMLDivElement} | 109 * @extends {HTMLDivElement} |
| 110 * @param {HTMLElement} tile Tile element that will be associated to the cell. |
| 111 * @param {Object} gridValues Tile page configuration object. |
40 */ | 112 */ |
41 function Tile(contents) { | 113 function TileCell(tile, gridValues) { |
42 var tile = cr.doc.createElement('div'); | 114 var tileCell = cr.doc.createElement('div'); |
43 tile.__proto__ = Tile.prototype; | 115 tileCell.__proto__ = TileCell.prototype; |
44 tile.initialize(contents); | 116 tileCell.initialize(tile, gridValues); |
45 | 117 |
46 return tile; | 118 return tileCell; |
47 } | 119 } |
48 | 120 |
49 Tile.prototype = { | 121 TileCell.prototype = { |
50 __proto__: HTMLDivElement.prototype, | 122 __proto__: HTMLDivElement.prototype, |
51 | 123 |
52 initialize: function(contents) { | 124 initialize: function(tile, gridValues) { |
53 // 'real' as opposed to doppleganger. | 125 // 'real' as opposed to doppleganger. |
54 this.className = 'tile real'; | 126 // TODO(xci) remove the real class. |
55 this.appendChild(contents); | 127 this.className = 'tile-cell real'; |
56 contents.tile = this; | |
57 | 128 |
58 this.addEventListener('dragstart', this.onDragStart_); | 129 if (gridValues.reinforceStyles) { |
59 this.addEventListener('drag', this.onDragMove_); | 130 var style = this.style; |
60 this.addEventListener('dragend', this.onDragEnd_); | 131 var borderWidth = 2 * gridValues.tileBorderWidth; |
| 132 style.width = gridValues.tileWidth + borderWidth + 'px'; |
| 133 style.height = gridValues.tileHeight + borderWidth + 'px'; |
| 134 // TODO(xci): should we change to marginRight for RTL languages? |
| 135 style.marginLeft = gridValues.tileHorMargin + 'px'; |
| 136 style.marginBottom = gridValues.tileVerMargin + 'px'; |
| 137 } |
61 | 138 |
62 this.firstChild.addEventListener( | 139 this.assign_(tile); |
63 'webkitAnimationEnd', this.onContentsAnimationEnd_.bind(this)); | |
64 | |
65 this.eventTracker = new EventTracker(); | |
66 }, | 140 }, |
67 | 141 |
68 get index() { | 142 get index() { |
69 return Array.prototype.indexOf.call(this.tilePage.tileElements_, this); | 143 return Array.prototype.indexOf.call(this.tilePage.tileElements_, |
| 144 this.firstChild); |
70 }, | 145 }, |
71 | 146 |
72 get tilePage() { | 147 get tilePage() { |
73 return findAncestorByClass(this, 'tile-page'); | 148 return findAncestorByClass(this, 'tile-page'); |
74 }, | 149 }, |
75 | 150 |
76 /** | 151 assign_: function(tile) { |
77 * Position the tile at |x, y|, and store this as the grid location, i.e. | 152 if (this.firstChild) { |
78 * where the tile 'belongs' when it's not being dragged. | 153 this.replaceChild(tile, this.firstChild); |
79 * @param {number} x The x coordinate, in pixels. | 154 } else { |
80 * @param {number} y The y coordinate, in pixels. | 155 this.appendChild(tile); |
81 */ | 156 } |
82 setGridPosition: function(x, y) { | 157 }, |
83 this.gridX = x; | 158 |
84 this.gridY = y; | 159 tearDown_: function() { |
85 this.moveTo(x, y); | |
86 }, | 160 }, |
87 | 161 |
88 /** | 162 /** |
89 * Position the tile at |x, y|. | |
90 * @param {number} x The x coordinate, in pixels. | |
91 * @param {number} y The y coordinate, in pixels. | |
92 */ | |
93 moveTo: function(x, y) { | |
94 // left overrides right in LTR, and right takes precedence in RTL. | |
95 this.style.left = toCssPx(x); | |
96 this.style.right = toCssPx(x); | |
97 this.style.top = toCssPx(y); | |
98 }, | |
99 | |
100 /** | |
101 * The handler for dragstart events fired on |this|. | 163 * The handler for dragstart events fired on |this|. |
102 * @param {Event} e The event for the drag. | 164 * @param {Event} e The event for the drag. |
103 * @private | 165 * @private |
104 */ | 166 */ |
| 167 // TODO(xci) drag |
105 onDragStart_: function(e) { | 168 onDragStart_: function(e) { |
106 // The user may start dragging again during a previous drag's finishing | 169 // The user may start dragging again during a previous drag's finishing |
107 // animation. | 170 // animation. |
108 if (this.classList.contains('dragging')) | 171 if (this.classList.contains('dragging')) |
109 this.finalizeDrag_(); | 172 this.finalizeDrag_(); |
110 | 173 |
111 setCurrentlyDraggingTile(this); | 174 setCurrentlyDraggingTile(this); |
112 | 175 |
113 e.dataTransfer.effectAllowed = 'copyMove'; | 176 e.dataTransfer.effectAllowed = 'copyMove'; |
114 this.firstChild.setDragData(e.dataTransfer); | 177 this.firstChild.setDragData(e.dataTransfer); |
(...skipping 19 matching lines...) Expand all Loading... |
134 this.parentNode.getBoundingClientRect().top; | 197 this.parentNode.getBoundingClientRect().top; |
135 | 198 |
136 this.onDragMove_(e); | 199 this.onDragMove_(e); |
137 }, | 200 }, |
138 | 201 |
139 /** | 202 /** |
140 * The handler for drag events fired on |this|. | 203 * The handler for drag events fired on |this|. |
141 * @param {Event} e The event for the drag. | 204 * @param {Event} e The event for the drag. |
142 * @private | 205 * @private |
143 */ | 206 */ |
| 207 // TODO(xci) drag |
144 onDragMove_: function(e) { | 208 onDragMove_: function(e) { |
145 if (e.view != window || (e.x == 0 && e.y == 0)) { | 209 if (e.view != window || (e.x == 0 && e.y == 0)) { |
146 this.dragClone.hidden = true; | 210 this.dragClone.hidden = true; |
147 return; | 211 return; |
148 } | 212 } |
149 | 213 |
150 this.dragClone.hidden = false; | 214 this.dragClone.hidden = false; |
151 this.dragClone.style.left = toCssPx(e.x - this.dragOffsetX); | 215 this.dragClone.style.left = toCssPx(e.x - this.dragOffsetX); |
152 this.dragClone.style.top = toCssPx(e.y - this.dragOffsetY); | 216 this.dragClone.style.top = toCssPx(e.y - this.dragOffsetY); |
153 }, | 217 }, |
154 | 218 |
155 /** | 219 /** |
156 * The handler for dragend events fired on |this|. | 220 * The handler for dragend events fired on |this|. |
157 * @param {Event} e The event for the drag. | 221 * @param {Event} e The event for the drag. |
158 * @private | 222 * @private |
159 */ | 223 */ |
| 224 // TODO(xci) drag |
160 onDragEnd_: function(e) { | 225 onDragEnd_: function(e) { |
161 this.dragClone.hidden = false; | 226 this.dragClone.hidden = false; |
162 this.dragClone.classList.add('placing'); | 227 this.dragClone.classList.add('placing'); |
163 | 228 |
164 setCurrentlyDraggingTile(null); | 229 setCurrentlyDraggingTile(null); |
165 | 230 |
166 // tilePage will be null if we've already been removed. | 231 // tilePage will be null if we've already been removed. |
167 var tilePage = this.tilePage; | 232 var tilePage = this.tilePage; |
168 if (tilePage) | 233 if (tilePage) |
169 tilePage.positionTile_(this.index); | 234 //tilePage.positionTile_(this.index); // TODO(xci) function was deleted! |
170 | 235 |
171 // Take an appropriate action with the drag clone. | 236 // Take an appropriate action with the drag clone. |
172 if (this.landedOnTrash) { | 237 if (this.landedOnTrash) { |
173 this.dragClone.classList.add('deleting'); | 238 this.dragClone.classList.add('deleting'); |
174 } else if (tilePage) { | 239 } else if (tilePage) { |
175 // TODO(dbeam): Until we fix dropEffect to the correct behavior it will | 240 // TODO(dbeam): Until we fix dropEffect to the correct behavior it will |
176 // differ on windows - crbug.com/39399. That's why we use the custom | 241 // differ on windows - crbug.com/39399. That's why we use the custom |
177 // this.lastDropEffect instead of e.dataTransfer.dropEffect. | 242 // this.lastDropEffect instead of e.dataTransfer.dropEffect. |
178 if (tilePage.selected && this.lastDropEffect != 'copy') { | 243 if (tilePage.selected && this.lastDropEffect != 'copy') { |
179 // The drag clone can still be hidden from the last drag move event. | 244 // The drag clone can still be hidden from the last drag move event. |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
212 this.landedOnTrash = false; | 277 this.landedOnTrash = false; |
213 }, | 278 }, |
214 | 279 |
215 /** | 280 /** |
216 * Creates a clone of this node offset by the coordinates. Used for the | 281 * Creates a clone of this node offset by the coordinates. Used for the |
217 * dragging effect where a tile appears to float off one side of the grid | 282 * dragging effect where a tile appears to float off one side of the grid |
218 * and re-appear on the other. | 283 * and re-appear on the other. |
219 * @param {number} x x-axis offset, in pixels. | 284 * @param {number} x x-axis offset, in pixels. |
220 * @param {number} y y-axis offset, in pixels. | 285 * @param {number} y y-axis offset, in pixels. |
221 */ | 286 */ |
| 287 // TODO(xci) drag |
222 showDoppleganger: function(x, y) { | 288 showDoppleganger: function(x, y) { |
223 // We always have to clear the previous doppleganger to make sure we get | 289 // We always have to clear the previous doppleganger to make sure we get |
224 // style updates for the contents of this tile. | 290 // style updates for the contents of this tile. |
225 this.clearDoppleganger(); | 291 this.clearDoppleganger(); |
226 | 292 |
227 var clone = this.cloneNode(true); | 293 var clone = this.cloneNode(true); |
228 clone.classList.remove('real'); | 294 clone.classList.remove('real'); |
229 clone.classList.add('doppleganger'); | 295 clone.classList.add('doppleganger'); |
230 var clonelets = clone.querySelectorAll('.real'); | 296 var clonelets = clone.querySelectorAll('.real'); |
231 for (var i = 0; i < clonelets.length; i++) { | 297 for (var i = 0; i < clonelets.length; i++) { |
232 clonelets[i].classList.remove('real'); | 298 clonelets[i].classList.remove('real'); |
233 } | 299 } |
234 | 300 |
235 this.appendChild(clone); | 301 this.appendChild(clone); |
236 this.doppleganger_ = clone; | 302 this.doppleganger_ = clone; |
237 | 303 |
238 if (isRTL()) | 304 if (isRTL()) |
239 x *= -1; | 305 x *= -1; |
240 | 306 |
241 this.doppleganger_.style.WebkitTransform = 'translate(' + x + 'px, ' + | 307 this.doppleganger_.style.WebkitTransform = 'translate(' + x + 'px, ' + |
242 y + 'px)'; | 308 y + 'px)'; |
243 }, | 309 }, |
244 | 310 |
245 /** | 311 /** |
246 * Destroys the current doppleganger. | 312 * Destroys the current doppleganger. |
247 */ | 313 */ |
| 314 // TODO(xci) drag |
248 clearDoppleganger: function() { | 315 clearDoppleganger: function() { |
249 if (this.doppleganger_) { | 316 if (this.doppleganger_) { |
250 this.removeChild(this.doppleganger_); | 317 this.removeChild(this.doppleganger_); |
251 this.doppleganger_ = null; | 318 this.doppleganger_ = null; |
252 } | 319 } |
253 }, | 320 }, |
254 | 321 |
255 /** | 322 /** |
256 * Returns status of doppleganger. | 323 * Returns status of doppleganger. |
257 * @return {boolean} True if there is a doppleganger showing for |this|. | 324 * @return {boolean} True if there is a doppleganger showing for |this|. |
258 */ | 325 */ |
| 326 // TODO(xci) drag |
259 hasDoppleganger: function() { | 327 hasDoppleganger: function() { |
260 return !!this.doppleganger_; | 328 return !!this.doppleganger_; |
261 }, | 329 }, |
262 | 330 |
263 /** | 331 /** |
264 * Cleans up after the drag is over. This is either called when the | 332 * Cleans up after the drag is over. This is either called when the |
265 * drag representation finishes animating to the final position, or when | 333 * drag representation finishes animating to the final position, or when |
266 * the next drag starts (if the user starts a 2nd drag very quickly). | 334 * the next drag starts (if the user starts a 2nd drag very quickly). |
267 * @private | 335 * @private |
268 */ | 336 */ |
| 337 // TODO(xci) drag |
269 finalizeDrag_: function() { | 338 finalizeDrag_: function() { |
270 assert(this.classList.contains('dragging')); | 339 assert(this.classList.contains('dragging')); |
271 | 340 |
272 var clone = this.dragClone; | 341 var clone = this.dragClone; |
273 this.dragClone = null; | 342 this.dragClone = null; |
274 | 343 |
275 clone.parentNode.removeChild(clone); | 344 clone.parentNode.removeChild(clone); |
276 this.eventTracker.remove(clone, 'webkitTransitionEnd'); | 345 this.eventTracker.remove(clone, 'webkitTransitionEnd'); |
277 this.classList.remove('dragging'); | 346 this.classList.remove('dragging'); |
278 if (this.firstChild.finalizeDrag) | 347 if (this.firstChild.finalizeDrag) |
279 this.firstChild.finalizeDrag(); | 348 this.firstChild.finalizeDrag(); |
280 }, | 349 }, |
281 | 350 |
282 /** | 351 /** |
283 * Called when the drag representation node is done migrating to its final | 352 * Called when the drag representation node is done migrating to its final |
284 * resting spot. | 353 * resting spot. |
285 * @param {Event} e The transition end event. | 354 * @param {Event} e The transition end event. |
286 */ | 355 */ |
| 356 // TODO(xci) drag |
287 onDragCloneTransitionEnd_: function(e) { | 357 onDragCloneTransitionEnd_: function(e) { |
288 if (this.classList.contains('dragging') && | 358 if (this.classList.contains('dragging') && |
289 (e.propertyName == 'left' || e.propertyName == 'top' || | 359 (e.propertyName == 'left' || e.propertyName == 'top' || |
290 e.propertyName == '-webkit-transform')) { | 360 e.propertyName == '-webkit-transform')) { |
291 this.finalizeDrag_(); | 361 this.finalizeDrag_(); |
292 } | 362 } |
293 }, | 363 }, |
294 | 364 |
295 /** | 365 /** |
296 * Called when an app is removed from Chrome. Animates its disappearance. | 366 * Called when an app is removed from Chrome. Animates its disappearance. |
(...skipping 12 matching lines...) Expand all Loading... |
309 */ | 379 */ |
310 onContentsAnimationEnd_: function(e) { | 380 onContentsAnimationEnd_: function(e) { |
311 if (this.firstChild.classList.contains('new-tile-contents')) | 381 if (this.firstChild.classList.contains('new-tile-contents')) |
312 this.firstChild.classList.remove('new-tile-contents'); | 382 this.firstChild.classList.remove('new-tile-contents'); |
313 if (this.firstChild.classList.contains('removing-tile-contents')) | 383 if (this.firstChild.classList.contains('removing-tile-contents')) |
314 this.tilePage.removeTile(this, true); | 384 this.tilePage.removeTile(this, true); |
315 }, | 385 }, |
316 }; | 386 }; |
317 | 387 |
318 /** | 388 /** |
319 * Gives the proportion of the row width that is devoted to a single icon. | |
320 * @param {number} rowTileCount The number of tiles in a row. | |
321 * @param {number} tileSpacingFraction The proportion of the tile width which | |
322 * will be used as spacing between tiles. | |
323 * @return {number} The ratio between icon width and row width. | |
324 */ | |
325 function tileWidthFraction(rowTileCount, tileSpacingFraction) { | |
326 return rowTileCount + (rowTileCount - 1) * tileSpacingFraction; | |
327 } | |
328 | |
329 /** | |
330 * Calculates an assortment of tile-related values for a grid with the | |
331 * given dimensions. | |
332 * @param {number} width The pixel width of the grid. | |
333 * @param {number} numRowTiles The number of tiles in a row. | |
334 * @param {number} tileSpacingFraction The proportion of the tile width which | |
335 * will be used as spacing between tiles. | |
336 * @return {Object} A mapping of pixel values. | |
337 */ | |
338 function tileValuesForGrid(width, numRowTiles, tileSpacingFraction) { | |
339 var tileWidth = width / tileWidthFraction(numRowTiles, tileSpacingFraction); | |
340 var offsetX = tileWidth * (1 + tileSpacingFraction); | |
341 var interTileSpacing = offsetX - tileWidth; | |
342 | |
343 return { | |
344 tileWidth: tileWidth, | |
345 offsetX: offsetX, | |
346 interTileSpacing: interTileSpacing, | |
347 }; | |
348 } | |
349 | |
350 // The smallest amount of horizontal blank space to display on the sides when | |
351 // displaying a wide arrangement. There is an additional 26px of margin from | |
352 // the tile page padding. | |
353 var MIN_WIDE_MARGIN = 18; | |
354 | |
355 /** | |
356 * Creates a new TilePage object. This object contains tiles and controls | 389 * Creates a new TilePage object. This object contains tiles and controls |
357 * their layout. | 390 * their layout. |
358 * @param {Object} gridValues Pixel values that define the size and layout | |
359 * of the tile grid. | |
360 * @constructor | 391 * @constructor |
361 * @extends {HTMLDivElement} | 392 * @extends {HTMLDivElement} |
362 */ | 393 */ |
363 function TilePage(gridValues) { | 394 function TilePage() { |
364 var el = cr.doc.createElement('div'); | 395 var el = cr.doc.createElement('div'); |
365 el.gridValues_ = gridValues; | |
366 el.__proto__ = TilePage.prototype; | 396 el.__proto__ = TilePage.prototype; |
367 el.initialize(); | 397 el.initialize(); |
368 | 398 |
369 return el; | 399 return el; |
370 } | 400 } |
371 | 401 |
372 /** | |
373 * Takes a collection of grid layout pixel values and updates them with | |
374 * additional tiling values that are calculated from TilePage constants. | |
375 * @param {Object} grid The grid layout pixel values to update. | |
376 */ | |
377 TilePage.initGridValues = function(grid) { | |
378 // The amount of space we need to display a narrow grid (all narrow grids | |
379 // are this size). | |
380 grid.narrowWidth = | |
381 grid.minTileWidth * tileWidthFraction(grid.minColCount, | |
382 grid.tileSpacingFraction); | |
383 // The minimum amount of space we need to display a wide grid. | |
384 grid.minWideWidth = | |
385 grid.minTileWidth * tileWidthFraction(grid.maxColCount, | |
386 grid.tileSpacingFraction); | |
387 // The largest we will ever display a wide grid. | |
388 grid.maxWideWidth = | |
389 grid.maxTileWidth * tileWidthFraction(grid.maxColCount, | |
390 grid.tileSpacingFraction); | |
391 // Tile-related pixel values for the narrow display. | |
392 grid.narrowTileValues = tileValuesForGrid(grid.narrowWidth, | |
393 grid.minColCount, | |
394 grid.tileSpacingFraction); | |
395 // Tile-related pixel values for the minimum narrow display. | |
396 grid.wideTileValues = tileValuesForGrid(grid.minWideWidth, | |
397 grid.maxColCount, | |
398 grid.tileSpacingFraction); | |
399 }; | |
400 | |
401 TilePage.prototype = { | 402 TilePage.prototype = { |
402 __proto__: HTMLDivElement.prototype, | 403 __proto__: HTMLDivElement.prototype, |
403 | 404 |
| 405 // The grid values should be defined by each TilePage subclass. |
| 406 gridValues_: { |
| 407 tileWidth: 0, |
| 408 tileHeight: 0, |
| 409 tileHorMargin: 0, // TODO margin with CSS / there's no margin in first col |
| 410 tileVerMargin: 0, |
| 411 tileBorderWidth: 0, |
| 412 bottomPanelHorMargin: 0, // TODO it doesn't make sense having this here. |
| 413 |
| 414 tileCount: 0, // TODO remove this dependency. rename it to maxTileCount. |
| 415 tileClassName: '', |
| 416 // TODO(pedrosimonetti): document this property? Consider removing it? |
| 417 reinforceStyles: true, |
| 418 |
| 419 // debug |
| 420 slowFactor: 1, |
| 421 debug: false |
| 422 }, |
| 423 |
| 424 get tileElements() { |
| 425 return this.tileElements_; |
| 426 }, |
| 427 |
404 initialize: function() { | 428 initialize: function() { |
405 this.className = 'tile-page'; | 429 this.className = 'tile-page'; |
406 | 430 |
407 // Div that acts as a custom scrollbar. The scrollbar has to live | |
408 // outside the content div so it doesn't flicker when scrolling (due to | |
409 // repainting after the scroll, then repainting again when moved in the | |
410 // onScroll handler). |scrollbar_| is only aesthetic, and it only | |
411 // represents the thumb. Actual events are still handled by the invisible | |
412 // native scrollbars. This div gives us more flexibility with the visuals. | |
413 this.scrollbar_ = this.ownerDocument.createElement('div'); | |
414 this.scrollbar_.className = 'tile-page-scrollbar'; | |
415 this.scrollbar_.hidden = true; | |
416 this.appendChild(this.scrollbar_); | |
417 | |
418 // This contains everything but the scrollbar. | 431 // This contains everything but the scrollbar. |
419 this.content_ = this.ownerDocument.createElement('div'); | 432 this.content_ = this.ownerDocument.createElement('div'); |
420 this.content_.className = 'tile-page-content'; | 433 this.content_.className = 'tile-page-content'; |
421 this.appendChild(this.content_); | 434 this.appendChild(this.content_); |
422 | 435 |
423 // Div that sets the vertical position of the tile grid. | |
424 this.topMargin_ = this.ownerDocument.createElement('div'); | |
425 this.topMargin_.className = 'top-margin'; | |
426 this.content_.appendChild(this.topMargin_); | |
427 | |
428 // Div that holds the tiles. | 436 // Div that holds the tiles. |
429 this.tileGrid_ = this.ownerDocument.createElement('div'); | 437 this.tileGrid_ = this.ownerDocument.createElement('div'); |
430 this.tileGrid_.className = 'tile-grid'; | 438 this.tileGrid_.className = 'tile-grid'; |
431 this.tileGrid_.style.minWidth = this.gridValues_.narrowWidth + 'px'; | 439 // TODO(xci) |
| 440 //this.tileGrid_.style.minWidth = this.gridValues_.narrowWidth + 'px'; |
432 this.content_.appendChild(this.tileGrid_); | 441 this.content_.appendChild(this.tileGrid_); |
433 | 442 |
| 443 // TODO(xci) new! |
| 444 this.tileGridContent_ = this.ownerDocument.createElement('div'); |
| 445 this.tileGridContent_.className = 'tile-grid-content'; |
| 446 this.tileGrid_.appendChild(this.tileGridContent_); |
| 447 |
| 448 // TODO(xci) new! |
| 449 this.tileGrid_.addEventListener('webkitTransitionEnd', |
| 450 this.onTileGridAnimationEnd_.bind(this)); |
| 451 |
434 // Ordered list of our tiles. | 452 // Ordered list of our tiles. |
435 this.tileElements_ = this.tileGrid_.getElementsByClassName('tile real'); | 453 //this.tileElements_ = this.tileGrid_.getElementsByClassName('tile real'); |
| 454 this.tileElements_ = []; |
| 455 |
436 // Ordered list of the elements which want to accept keyboard focus. These | 456 // Ordered list of the elements which want to accept keyboard focus. These |
437 // elements will not be a part of the normal tab order; the tile grid | 457 // elements will not be a part of the normal tab order; the tile grid |
438 // initially gets focused and then these elements can be focused via the | 458 // initially gets focused and then these elements can be focused via the |
439 // arrow keys. | 459 // arrow keys. |
| 460 // TODO(xci) |
| 461 /* |
440 this.focusableElements_ = | 462 this.focusableElements_ = |
441 this.tileGrid_.getElementsByClassName('focusable'); | 463 this.tileGrid_.getElementsByClassName('focusable'); |
442 | 464 */ |
443 // These are properties used in updateTopMargin. | |
444 this.animatedTopMarginPx_ = 0; | |
445 this.topMarginPx_ = 0; | |
446 | 465 |
447 this.eventTracker = new EventTracker(); | 466 this.eventTracker = new EventTracker(); |
448 this.eventTracker.add(window, 'resize', this.onResize_.bind(this)); | 467 this.eventTracker.add(window, 'resize', this.onResize_.bind(this)); |
| 468 // TODO(xci) new |
| 469 this.eventTracker.add(window, 'keyup', this.onKeyUp_.bind(this)); |
449 | 470 |
450 this.addEventListener('DOMNodeInsertedIntoDocument', | 471 // TODO(xci) |
451 this.onNodeInsertedIntoDocument_); | 472 //this.addEventListener('DOMNodeInsertedIntoDocument', |
| 473 // this.onNodeInsertedIntoDocument_); |
452 | 474 |
453 this.content_.addEventListener('scroll', this.onScroll_.bind(this)); | 475 //this.dragWrapper_ = new cr.ui.DragWrapper(this.tileGrid_, this); |
454 | |
455 this.dragWrapper_ = new cr.ui.DragWrapper(this.tileGrid_, this); | |
456 | 476 |
457 this.addEventListener('cardselected', this.handleCardSelection_); | 477 this.addEventListener('cardselected', this.handleCardSelection_); |
458 this.addEventListener('carddeselected', this.handleCardDeselection_); | 478 this.addEventListener('carddeselected', this.handleCardDeselection_); |
459 this.addEventListener('focus', this.handleFocus_); | 479 //this.addEventListener('focus', this.handleFocus_); |
460 this.addEventListener('keydown', this.handleKeyDown_); | 480 //this.addEventListener('keydown', this.handleKeyDown_); |
461 this.addEventListener('mousedown', this.handleMouseDown_); | 481 //this.addEventListener('mousedown', this.handleMouseDown_); |
462 | 482 |
463 this.focusElementIndex_ = -1; | 483 //this.focusElementIndex_ = -1; |
464 }, | 484 }, |
465 | 485 |
466 get tiles() { | 486 get tiles() { |
467 return this.tileElements_; | 487 return this.tileElements_; |
468 }, | 488 }, |
469 | 489 |
470 get tileCount() { | 490 get tileCount() { |
471 return this.tileElements_.length; | 491 return this.tileElements_.length; |
472 }, | 492 }, |
473 | 493 |
474 get selected() { | 494 get selected() { |
475 return Array.prototype.indexOf.call(this.parentNode.children, this) == | 495 return Array.prototype.indexOf.call(this.parentNode.children, this) == |
476 ntp.getCardSlider().currentCard; | 496 ntp.getCardSlider().currentCard; |
477 }, | 497 }, |
478 | 498 |
479 /** | 499 /** |
480 * The size of the margin (unused space) on the sides of the tile grid, in | |
481 * pixels. | |
482 * @type {number} | |
483 */ | |
484 get sideMargin() { | |
485 return this.layoutValues_.leftMargin; | |
486 }, | |
487 | |
488 /** | |
489 * Returns the width of the scrollbar, in pixels, if it is active, or 0 | |
490 * otherwise. | |
491 * @type {number} | |
492 */ | |
493 get scrollbarWidth() { | |
494 return this.scrollbar_.hidden ? 0 : 13; | |
495 }, | |
496 | |
497 /** | |
498 * Returns any extra padding to insert to the bottom of a tile page. By | |
499 * default there is none, but subclasses can override. | |
500 * @type {number} | |
501 */ | |
502 get extraBottomPadding() { | |
503 return 0; | |
504 }, | |
505 | |
506 /** | |
507 * The notification content of this tile (if any, otherwise null). | 500 * The notification content of this tile (if any, otherwise null). |
508 * @type {!HTMLElement} | 501 * @type {!HTMLElement} |
509 */ | 502 */ |
510 get notification() { | 503 get notification() { |
511 return this.topMargin_.nextElementSibling.id == 'notification-container' ? | 504 return this.content_.firstChild.id == 'notification-container' ? |
512 this.topMargin_.nextElementSibling : null; | 505 this.content_.firstChild : null; |
513 }, | 506 }, |
514 /** | 507 /** |
515 * The notification content of this tile (if any, otherwise null). | 508 * The notification content of this tile (if any, otherwise null). |
516 * @type {!HTMLElement} | 509 * @type {!HTMLElement} |
517 */ | 510 */ |
518 set notification(node) { | 511 set notification(node) { |
519 assert(node instanceof HTMLElement, '|node| isn\'t an HTMLElement!'); | 512 assert(node instanceof HTMLElement, '|node| isn\'t an HTMLElement!'); |
520 // NOTE: Implicitly removes from DOM if |node| is inside it. | 513 // NOTE: Implicitly removes from DOM if |node| is inside it. |
521 this.content_.insertBefore(node, this.topMargin_.nextElementSibling); | 514 this.content_.insertBefore(node, this.content_.firstChild); |
522 this.positionNotification_(); | 515 this.positionNotification_(); |
523 }, | 516 }, |
524 | 517 |
525 /** | 518 /** |
526 * Fetches the size, in pixels, of the padding-top of the tile contents. | |
527 * @type {number} | |
528 */ | |
529 get contentPadding() { | |
530 if (typeof this.contentPadding_ == 'undefined') { | |
531 this.contentPadding_ = | |
532 parseInt(getComputedStyle(this.content_).paddingTop, 10); | |
533 } | |
534 return this.contentPadding_; | |
535 }, | |
536 | |
537 /** | |
538 * Removes the tilePage from the DOM and cleans up event handlers. | 519 * Removes the tilePage from the DOM and cleans up event handlers. |
539 */ | 520 */ |
540 remove: function() { | 521 remove: function() { |
541 // This checks arguments.length as most remove functions have a boolean | 522 // This checks arguments.length as most remove functions have a boolean |
542 // |opt_animate| argument, but that's not necesarilly applicable to | 523 // |opt_animate| argument, but that's not necesarilly applicable to |
543 // removing a tilePage. Selecting a different card in an animated way and | 524 // removing a tilePage. Selecting a different card in an animated way and |
544 // deleting the card afterward is probably a better choice. | 525 // deleting the card afterward is probably a better choice. |
545 assert(typeof arguments[0] != 'boolean', | 526 assert(typeof arguments[0] != 'boolean', |
546 'This function takes no |opt_animate| argument.'); | 527 'This function takes no |opt_animate| argument.'); |
547 this.tearDown_(); | 528 this.tearDown_(); |
548 this.parentNode.removeChild(this); | 529 this.parentNode.removeChild(this); |
549 }, | 530 }, |
550 | 531 |
551 /** | 532 /** |
552 * Cleans up resources that are no longer needed after this TilePage | 533 * Cleans up resources that are no longer needed after this TilePage |
553 * instance is removed from the DOM. | 534 * instance is removed from the DOM. |
554 * @private | 535 * @private |
555 */ | 536 */ |
556 tearDown_: function() { | 537 tearDown_: function() { |
557 this.eventTracker.removeAll(); | 538 this.eventTracker.removeAll(); |
558 }, | 539 }, |
559 | 540 |
560 /** | 541 /** |
561 * Appends a tile to the end of the tile grid. | 542 * Appends a tile to the end of the tile grid. |
562 * @param {HTMLElement} tileElement The contents of the tile. | 543 * @param {HTMLElement} tileElement The contents of the tile. |
563 * @param {boolean} animate If true, the append will be animated. | 544 * @param {boolean} animate If true, the append will be animated. |
564 * @protected | 545 * @protected |
565 */ | 546 */ |
| 547 // TODO(xci) |
| 548 /* |
566 appendTile: function(tileElement, animate) { | 549 appendTile: function(tileElement, animate) { |
567 this.addTileAt(tileElement, this.tileElements_.length, animate); | 550 this.addTileAt(tileElement, this.tileElements_.length, animate); |
568 }, | 551 }, |
| 552 */ |
569 | 553 |
570 /** | 554 /** |
571 * Adds the given element to the tile grid. | 555 * Adds the given element to the tile grid. |
572 * @param {Node} tileElement The tile object/node to insert. | 556 * @param {Node} tileElement The tile object/node to insert. |
573 * @param {number} index The location in the tile grid to insert it at. | 557 * @param {number} index The location in the tile grid to insert it at. |
574 * @param {boolean} animate If true, the tile in question will be | 558 * @param {boolean} animate If true, the tile in question will be |
575 * animated (other tiles, if they must reposition, do not animate). | 559 * animated (other tiles, if they must reposition, do not animate). |
576 * @protected | 560 * @protected |
577 */ | 561 */ |
| 562 // TODO(xci) |
| 563 /* |
578 addTileAt: function(tileElement, index, animate) { | 564 addTileAt: function(tileElement, index, animate) { |
579 this.classList.remove('animating-tile-page'); | |
580 if (animate) | |
581 tileElement.classList.add('new-tile-contents'); | |
582 | |
583 // Make sure the index is positive and either in the the bounds of | |
584 // this.tileElements_ or at the end (meaning append). | |
585 assert(index >= 0 && index <= this.tileElements_.length); | |
586 | |
587 var wrapperDiv = new Tile(tileElement); | |
588 // If is out of the bounds of the tile element list, .insertBefore() will | |
589 // act just like appendChild(). | |
590 this.tileGrid_.insertBefore(wrapperDiv, this.tileElements_[index]); | |
591 this.calculateLayoutValues_(); | 565 this.calculateLayoutValues_(); |
592 this.heightChanged_(); | |
593 | |
594 this.repositionTiles_(); | |
595 this.fireAddedEvent(wrapperDiv, index, animate); | 566 this.fireAddedEvent(wrapperDiv, index, animate); |
596 }, | 567 }, |
| 568 */ |
597 | 569 |
598 /** | 570 /** |
599 * Notify interested subscribers that a tile has been removed from this | 571 * Notify interested subscribers that a tile has been removed from this |
600 * page. | 572 * page. |
601 * @param {Tile} tile The newly added tile. | 573 * @param {TileCell} tile The newly added tile. |
602 * @param {number} index The index of the tile that was added. | 574 * @param {number} index The index of the tile that was added. |
603 * @param {boolean} wasAnimated Whether the removal was animated. | 575 * @param {boolean} wasAnimated Whether the removal was animated. |
604 */ | 576 */ |
605 fireAddedEvent: function(tile, index, wasAnimated) { | 577 fireAddedEvent: function(tile, index, wasAnimated) { |
606 var e = document.createEvent('Event'); | 578 var e = document.createEvent('Event'); |
607 e.initEvent('tilePage:tile_added', true, true); | 579 e.initEvent('tilePage:tile_added', true, true); |
608 e.addedIndex = index; | 580 e.addedIndex = index; |
609 e.addedTile = tile; | 581 e.addedTile = tile; |
610 e.wasAnimated = wasAnimated; | 582 e.wasAnimated = wasAnimated; |
611 this.dispatchEvent(e); | 583 this.dispatchEvent(e); |
(...skipping 13 matching lines...) Expand all Loading... |
625 this.calculateLayoutValues_(); | 597 this.calculateLayoutValues_(); |
626 this.cleanupDrag(); | 598 this.cleanupDrag(); |
627 | 599 |
628 if (!opt_dontNotify) | 600 if (!opt_dontNotify) |
629 this.fireRemovedEvent(tile, index, !!opt_animate); | 601 this.fireRemovedEvent(tile, index, !!opt_animate); |
630 }, | 602 }, |
631 | 603 |
632 /** | 604 /** |
633 * Notify interested subscribers that a tile has been removed from this | 605 * Notify interested subscribers that a tile has been removed from this |
634 * page. | 606 * page. |
635 * @param {Tile} tile The tile that was removed. | 607 * @param {TileCell} tile The tile that was removed. |
636 * @param {number} oldIndex Where the tile was positioned before removal. | 608 * @param {number} oldIndex Where the tile was positioned before removal. |
637 * @param {boolean} wasAnimated Whether the removal was animated. | 609 * @param {boolean} wasAnimated Whether the removal was animated. |
638 */ | 610 */ |
639 fireRemovedEvent: function(tile, oldIndex, wasAnimated) { | 611 fireRemovedEvent: function(tile, oldIndex, wasAnimated) { |
640 var e = document.createEvent('Event'); | 612 var e = document.createEvent('Event'); |
641 e.initEvent('tilePage:tile_removed', true, true); | 613 e.initEvent('tilePage:tile_removed', true, true); |
642 e.removedIndex = oldIndex; | 614 e.removedIndex = oldIndex; |
643 e.removedTile = tile; | 615 e.removedTile = tile; |
644 e.wasAnimated = wasAnimated; | 616 e.wasAnimated = wasAnimated; |
645 this.dispatchEvent(e); | 617 this.dispatchEvent(e); |
646 }, | 618 }, |
647 | 619 |
648 /** | 620 /** |
649 * Removes all tiles from the page. | 621 * Removes all tiles from the page. |
650 */ | 622 */ |
651 removeAllTiles: function() { | 623 removeAllTiles: function() { |
| 624 // TODO(xci) dispatch individual tearDown functions |
652 this.tileGrid_.innerHTML = ''; | 625 this.tileGrid_.innerHTML = ''; |
653 }, | 626 }, |
654 | 627 |
655 /** | 628 /** |
656 * Called when the page is selected (in the card selector). | 629 * Called when the page is selected (in the card selector). |
657 * @param {Event} e A custom cardselected event. | 630 * @param {Event} e A custom cardselected event. |
658 * @private | 631 * @private |
659 */ | 632 */ |
660 handleCardSelection_: function(e) { | 633 handleCardSelection_: function(e) { |
661 this.tabIndex = 1; | 634 this.tabIndex = 1; |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
790 }, | 763 }, |
791 | 764 |
792 /** | 765 /** |
793 * Makes some calculations for tile layout. These change depending on | 766 * Makes some calculations for tile layout. These change depending on |
794 * height, width, and the number of tiles. | 767 * height, width, and the number of tiles. |
795 * TODO(estade): optimize calls to this function. Do nothing if the page is | 768 * TODO(estade): optimize calls to this function. Do nothing if the page is |
796 * hidden, but call before being shown. | 769 * hidden, but call before being shown. |
797 * @private | 770 * @private |
798 */ | 771 */ |
799 calculateLayoutValues_: function() { | 772 calculateLayoutValues_: function() { |
800 var grid = this.gridValues_; | 773 this.layout_(); |
801 var availableSpace = this.tileGrid_.clientWidth - 2 * MIN_WIDE_MARGIN; | |
802 var wide = availableSpace >= grid.minWideWidth; | |
803 var numRowTiles = wide ? grid.maxColCount : grid.minColCount; | |
804 | |
805 var effectiveGridWidth = wide ? | |
806 Math.min(Math.max(availableSpace, grid.minWideWidth), | |
807 grid.maxWideWidth) : | |
808 grid.narrowWidth; | |
809 var realTileValues = tileValuesForGrid(effectiveGridWidth, numRowTiles, | |
810 grid.tileSpacingFraction); | |
811 | |
812 // leftMargin centers the grid within the avaiable space. | |
813 var minMargin = wide ? MIN_WIDE_MARGIN : 0; | |
814 var leftMargin = | |
815 Math.max(minMargin, | |
816 (this.tileGrid_.clientWidth - effectiveGridWidth) / 2); | |
817 | |
818 var rowHeight = this.heightForWidth(realTileValues.tileWidth) + | |
819 realTileValues.interTileSpacing; | |
820 | |
821 this.layoutValues_ = { | |
822 colWidth: realTileValues.offsetX, | |
823 gridWidth: effectiveGridWidth, | |
824 leftMargin: leftMargin, | |
825 numRowTiles: numRowTiles, | |
826 rowHeight: rowHeight, | |
827 tileWidth: realTileValues.tileWidth, | |
828 wide: wide, | |
829 }; | |
830 | 774 |
831 // We need to update the top margin as well. | 775 // We need to update the top margin as well. |
832 this.updateTopMargin_(); | 776 this.updateTopMargin_(); |
833 | 777 |
| 778 // TODO(pedrosimonetti): when do we really need to send this message? |
834 this.firePageLayoutEvent_(); | 779 this.firePageLayoutEvent_(); |
835 }, | 780 }, |
836 | 781 |
837 /** | 782 /** |
838 * Dispatches the custom pagelayout event. | 783 * Dispatches the custom pagelayout event. |
839 * @private | 784 * @private |
840 */ | 785 */ |
841 firePageLayoutEvent_: function() { | 786 firePageLayoutEvent_: function() { |
842 cr.dispatchSimpleEvent(this, 'pagelayout', true, true); | 787 cr.dispatchSimpleEvent(this, 'pagelayout', true, true); |
843 }, | 788 }, |
844 | 789 |
845 /** | |
846 * @return {number} The amount of margin that should be animated (in pixels) | |
847 * for the current grid layout. | |
848 */ | |
849 getAnimatedLeftMargin_: function() { | |
850 if (this.layoutValues_.wide) | |
851 return 0; | |
852 | |
853 var grid = this.gridValues_; | |
854 return (grid.minWideWidth - MIN_WIDE_MARGIN - grid.narrowWidth) / 2; | |
855 }, | |
856 | |
857 /** | |
858 * Calculates the x/y coordinates for an element and moves it there. | |
859 * @param {number} index The index of the element to be positioned. | |
860 * @param {number} indexOffset If provided, this is added to |index| when | |
861 * positioning the tile. The effect is that the tile will be positioned | |
862 * in a non-default location. | |
863 * @private | |
864 */ | |
865 positionTile_: function(index, indexOffset) { | |
866 var grid = this.gridValues_; | |
867 var layout = this.layoutValues_; | |
868 | |
869 indexOffset = typeof indexOffset != 'undefined' ? indexOffset : 0; | |
870 // Add the offset _after_ the modulus division. We might want to show the | |
871 // tile off the side of the grid. | |
872 var col = index % layout.numRowTiles + indexOffset; | |
873 var row = Math.floor(index / layout.numRowTiles); | |
874 // Calculate the final on-screen position for the tile. | |
875 var realX = col * layout.colWidth + layout.leftMargin; | |
876 var realY = row * layout.rowHeight; | |
877 | |
878 // Calculate the portion of the tile's position that should be animated. | |
879 var animatedTileValues = layout.wide ? | |
880 grid.wideTileValues : grid.narrowTileValues; | |
881 // Animate the difference between three-wide and six-wide. | |
882 var animatedLeftMargin = this.getAnimatedLeftMargin_(); | |
883 var animatedX = col * animatedTileValues.offsetX + animatedLeftMargin; | |
884 var animatedY = row * (this.heightForWidth(animatedTileValues.tileWidth) + | |
885 animatedTileValues.interTileSpacing); | |
886 | |
887 var tile = this.tileElements_[index]; | |
888 tile.setGridPosition(animatedX, animatedY); | |
889 tile.firstChild.setBounds(layout.tileWidth, | |
890 realX - animatedX, | |
891 realY - animatedY); | |
892 | |
893 // This code calculates whether the tile needs to show a clone of itself | |
894 // wrapped around the other side of the tile grid. | |
895 var offTheRight = col == layout.numRowTiles || | |
896 (col == layout.numRowTiles - 1 && tile.hasDoppleganger()); | |
897 var offTheLeft = col == -1 || (col == 0 && tile.hasDoppleganger()); | |
898 if (this.isCurrentDragTarget && (offTheRight || offTheLeft)) { | |
899 var sign = offTheRight ? 1 : -1; | |
900 tile.showDoppleganger(-layout.numRowTiles * layout.colWidth * sign, | |
901 layout.rowHeight * sign); | |
902 } else { | |
903 tile.clearDoppleganger(); | |
904 } | |
905 | |
906 if (index == this.tileElements_.length - 1) { | |
907 this.tileGrid_.style.height = (realY + layout.rowHeight) + 'px'; | |
908 this.queueUpdateScrollbars_(); | |
909 } | |
910 }, | |
911 | 790 |
912 /** | 791 /** |
913 * Gets the index of the tile that should occupy coordinate (x, y). Note | 792 * Gets the index of the tile that should occupy coordinate (x, y). Note |
914 * that this function doesn't care where the tiles actually are, and will | 793 * that this function doesn't care where the tiles actually are, and will |
915 * return an index even for the space between two tiles. This function is | 794 * return an index even for the space between two tiles. This function is |
916 * effectively the inverse of |positionTile_|. | 795 * effectively the inverse of |positionTile_|. |
917 * @param {number} x The x coordinate, in pixels, relative to the left of | 796 * @param {number} x The x coordinate, in pixels, relative to the left of |
918 * |this|. | 797 * |this|. |
919 * @param {number} y The y coordinate, in pixels, relative to the top of | 798 * @param {number} y The y coordinate, in pixels, relative to the top of |
920 * |this|. | 799 * |this|. |
921 * @private | 800 * @private |
922 */ | 801 */ |
| 802 // TODO(xci) drag |
923 getWouldBeIndexForPoint_: function(x, y) { | 803 getWouldBeIndexForPoint_: function(x, y) { |
924 var grid = this.gridValues_; | 804 var grid = this.gridValues_; |
925 var layout = this.layoutValues_; | 805 var layout = this.layoutValues_; |
926 | 806 |
927 var gridClientRect = this.tileGrid_.getBoundingClientRect(); | 807 var gridClientRect = this.tileGrid_.getBoundingClientRect(); |
928 var col = Math.floor((x - gridClientRect.left - layout.leftMargin) / | 808 var col = Math.floor((x - gridClientRect.left - layout.leftMargin) / |
929 layout.colWidth); | 809 layout.colWidth); |
930 if (col < 0 || col >= layout.numRowTiles) | 810 if (col < 0 || col >= layout.numRowTiles) |
931 return -1; | 811 return -1; |
932 | 812 |
933 if (isRTL()) | 813 if (isRTL()) |
934 col = layout.numRowTiles - 1 - col; | 814 col = layout.numRowTiles - 1 - col; |
935 | 815 |
936 var row = Math.floor((y - gridClientRect.top) / layout.rowHeight); | 816 var row = Math.floor((y - gridClientRect.top) / layout.rowHeight); |
937 return row * layout.numRowTiles + col; | 817 return row * layout.numRowTiles + col; |
938 }, | 818 }, |
939 | 819 |
940 /** | 820 /** |
941 * Window resize event handler. Window resizes may trigger re-layouts. | |
942 * @param {Object} e The resize event. | |
943 */ | |
944 onResize_: function(e) { | |
945 if (this.lastWidth_ == this.clientWidth && | |
946 this.lastHeight_ == this.clientHeight) { | |
947 return; | |
948 } | |
949 | |
950 this.calculateLayoutValues_(); | |
951 | |
952 this.lastWidth_ = this.clientWidth; | |
953 this.lastHeight_ = this.clientHeight; | |
954 this.classList.add('animating-tile-page'); | |
955 this.heightChanged_(); | |
956 | |
957 this.positionNotification_(); | |
958 this.repositionTiles_(); | |
959 }, | |
960 | |
961 /** | |
962 * The tile grid has an image mask which fades at the edges. We only show | 821 * The tile grid has an image mask which fades at the edges. We only show |
963 * the mask when there is an active drag; it obscures doppleganger tiles | 822 * the mask when there is an active drag; it obscures doppleganger tiles |
964 * as they enter or exit the grid. | 823 * as they enter or exit the grid. |
965 * @private | 824 * @private |
966 */ | 825 */ |
| 826 // TODO(xci) drag |
967 updateMask_: function() { | 827 updateMask_: function() { |
968 if (!this.isCurrentDragTarget) { | 828 if (!this.isCurrentDragTarget) { |
969 this.tileGrid_.style.WebkitMaskBoxImage = ''; | 829 this.tileGrid_.style.WebkitMaskBoxImage = ''; |
970 return; | 830 return; |
971 } | 831 } |
972 | 832 |
973 var leftMargin = this.layoutValues_.leftMargin; | 833 var leftMargin = this.layoutValues_.leftMargin; |
974 // The fade distance is the space between tiles. | 834 // The fade distance is the space between tiles. |
975 var fadeDistance = (this.gridValues_.tileSpacingFraction * | 835 var fadeDistance = (this.gridValues_.tileSpacingFraction * |
976 this.layoutValues_.tileWidth); | 836 this.layoutValues_.tileWidth); |
977 fadeDistance = Math.min(leftMargin, fadeDistance); | 837 fadeDistance = Math.min(leftMargin, fadeDistance); |
978 // On Skia we don't use any fade because it works very poorly. See | 838 // On Skia we don't use any fade because it works very poorly. See |
979 // http://crbug.com/99373 | 839 // http://crbug.com/99373 |
980 if (!cr.isMac) | 840 if (!cr.isMac) |
981 fadeDistance = 1; | 841 fadeDistance = 1; |
982 var gradient = | 842 var gradient = |
983 '-webkit-linear-gradient(left,' + | 843 '-webkit-linear-gradient(left,' + |
984 'transparent, ' + | 844 'transparent, ' + |
985 'transparent ' + (leftMargin - fadeDistance) + 'px, ' + | 845 'transparent ' + (leftMargin - fadeDistance) + 'px, ' + |
986 'black ' + leftMargin + 'px, ' + | 846 'black ' + leftMargin + 'px, ' + |
987 'black ' + (this.tileGrid_.clientWidth - leftMargin) + 'px, ' + | 847 'black ' + (this.tileGrid_.clientWidth - leftMargin) + 'px, ' + |
988 'transparent ' + (this.tileGrid_.clientWidth - leftMargin + | 848 'transparent ' + (this.tileGrid_.clientWidth - leftMargin + |
989 fadeDistance) + 'px, ' + | 849 fadeDistance) + 'px, ' + |
990 'transparent)'; | 850 'transparent)'; |
991 this.tileGrid_.style.WebkitMaskBoxImage = gradient; | 851 this.tileGrid_.style.WebkitMaskBoxImage = gradient; |
992 }, | 852 }, |
993 | 853 |
| 854 // TODO(xci) delete (used by drag and drop) |
994 updateTopMargin_: function() { | 855 updateTopMargin_: function() { |
995 var layout = this.layoutValues_; | 856 return; |
996 | |
997 // The top margin is set so that the vertical midpoint of the grid will | |
998 // be 1/3 down the page. | |
999 var numTiles = this.tileCount + | |
1000 (this.isCurrentDragTarget && !this.withinPageDrag_ ? 1 : 0); | |
1001 var numRows = Math.max(1, Math.ceil(numTiles / layout.numRowTiles)); | |
1002 var usedHeight = layout.rowHeight * numRows; | |
1003 var newMargin = document.documentElement.clientHeight / 3 - | |
1004 usedHeight / 3 - this.contentPadding; | |
1005 // The 'height' style attribute of topMargin is non-zero to work around | |
1006 // webkit's collapsing margin behavior, so we have to factor that into | |
1007 // our calculations here. | |
1008 newMargin = Math.max(newMargin, 0) - this.topMargin_.offsetHeight; | |
1009 | |
1010 // |newMargin| is the final margin we actually want to show. However, | |
1011 // part of that should be animated and part should not (for the same | |
1012 // reason as with leftMargin). The approach is to consider differences | |
1013 // when the layout changes from wide to narrow or vice versa as | |
1014 // 'animatable'. These differences accumulate in animatedTopMarginPx_, | |
1015 // while topMarginPx_ caches the real (total) margin. Either of these | |
1016 // calculations may come out to be negative, so we use margins as the | |
1017 // css property. | |
1018 | |
1019 if (typeof this.topMarginIsForWide_ == 'undefined') | |
1020 this.topMarginIsForWide_ = layout.wide; | |
1021 if (this.topMarginIsForWide_ != layout.wide) { | |
1022 this.animatedTopMarginPx_ += newMargin - this.topMarginPx_; | |
1023 this.topMargin_.style.marginBottom = toCssPx(this.animatedTopMarginPx_); | |
1024 } | |
1025 | |
1026 this.topMarginIsForWide_ = layout.wide; | |
1027 this.topMarginPx_ = newMargin; | |
1028 this.topMargin_.style.marginTop = | |
1029 toCssPx(this.topMarginPx_ - this.animatedTopMarginPx_); | |
1030 }, | 857 }, |
1031 | 858 |
1032 /** | 859 /** |
1033 * Position the notification if there's one showing. | 860 * Position the notification if there's one showing. |
1034 */ | 861 */ |
1035 positionNotification_: function() { | 862 positionNotification_: function() { |
1036 var notification = this.notification; | 863 if (this.notification && !this.notification.hidden) { |
1037 if (!notification || notification.hidden) | |
1038 return; | 864 return; |
1039 | 865 this.notification.style.margin = |
1040 // Update the horizontal position. | 866 -this.notification.offsetHeight + 'px ' + |
1041 var animatedLeftMargin = this.getAnimatedLeftMargin_(); | 867 this.layoutValues_.leftMargin + 'px 0'; |
1042 notification.style.WebkitMarginStart = animatedLeftMargin + 'px'; | 868 } |
1043 var leftOffset = (this.layoutValues_.leftMargin - animatedLeftMargin) * | |
1044 (isRTL() ? -1 : 1); | |
1045 notification.style.WebkitTransform = 'translateX(' + leftOffset + 'px)'; | |
1046 | |
1047 // Update the allowable widths of the text. | |
1048 var buttonWidth = notification.querySelector('button').offsetWidth + 8; | |
1049 notification.querySelector('span').style.maxWidth = | |
1050 this.layoutValues_.gridWidth - buttonWidth + 'px'; | |
1051 | |
1052 // This makes sure the text doesn't condense smaller than the narrow size | |
1053 // of the grid (e.g. when a user makes the window really small). | |
1054 notification.style.minWidth = | |
1055 this.gridValues_.narrowWidth - buttonWidth + 'px'; | |
1056 | |
1057 // Update the top position. | |
1058 notification.style.marginTop = -notification.offsetHeight + 'px'; | |
1059 }, | 869 }, |
1060 | 870 |
1061 /** | 871 /** |
1062 * Handles final setup that can only happen after |this| is inserted into | 872 * Handles final setup that can only happen after |this| is inserted into |
1063 * the page. | 873 * the page. |
1064 * @private | 874 * @private |
1065 */ | 875 */ |
1066 onNodeInsertedIntoDocument_: function(e) { | 876 onNodeInsertedIntoDocument_: function(e) { |
1067 this.calculateLayoutValues_(); | 877 this.calculateLayoutValues_(); |
1068 this.heightChanged_(); | |
1069 }, | |
1070 | |
1071 /** | |
1072 * Called when the height of |this| has changed: update the size of | |
1073 * tileGrid. | |
1074 * @private | |
1075 */ | |
1076 heightChanged_: function() { | |
1077 // The tile grid will expand to the bottom footer, or enough to hold all | |
1078 // the tiles, whichever is greater. It would be nicer if tilePage were | |
1079 // a flex box, and the tile grid could be box-flex: 1, but this exposes a | |
1080 // bug where repositioning tiles will cause the scroll position to reset. | |
1081 this.tileGrid_.style.minHeight = (this.clientHeight - | |
1082 this.tileGrid_.offsetTop - this.content_.offsetTop - | |
1083 this.extraBottomPadding - | |
1084 (this.footerNode_ ? this.footerNode_.clientHeight : 0)) + 'px'; | |
1085 }, | 878 }, |
1086 | 879 |
1087 /** | 880 /** |
1088 * Places an element at the bottom of the content div. Used in bare-minimum | 881 * Places an element at the bottom of the content div. Used in bare-minimum |
1089 * mode to hold #footer. | 882 * mode to hold #footer. |
1090 * @param {HTMLElement} footerNode The node to append to content. | 883 * @param {HTMLElement} footerNode The node to append to content. |
1091 */ | 884 */ |
1092 appendFooter: function(footerNode) { | 885 appendFooter: function(footerNode) { |
1093 this.footerNode_ = footerNode; | 886 this.footerNode_ = footerNode; |
1094 this.content_.appendChild(footerNode); | 887 this.content_.appendChild(footerNode); |
(...skipping 10 matching lines...) Expand all Loading... |
1105 * @param {Event} e The mousewheel event. | 898 * @param {Event} e The mousewheel event. |
1106 */ | 899 */ |
1107 handleMouseWheel: function(e) { | 900 handleMouseWheel: function(e) { |
1108 if (e.wheelDeltaY == 0) | 901 if (e.wheelDeltaY == 0) |
1109 return false; | 902 return false; |
1110 | 903 |
1111 this.content_.scrollTop -= e.wheelDeltaY / 3; | 904 this.content_.scrollTop -= e.wheelDeltaY / 3; |
1112 return true; | 905 return true; |
1113 }, | 906 }, |
1114 | 907 |
1115 /** | |
1116 * Handler for the 'scroll' event on |content_|. | |
1117 * @param {Event} e The scroll event. | |
1118 * @private | |
1119 */ | |
1120 onScroll_: function(e) { | |
1121 this.queueUpdateScrollbars_(); | |
1122 }, | |
1123 | |
1124 /** | |
1125 * ID of scrollbar update timer. If 0, there's no scrollbar re-calc queued. | |
1126 * @private | |
1127 */ | |
1128 scrollbarUpdate_: 0, | |
1129 | |
1130 /** | |
1131 * Queues an update on the custom scrollbar. Used for two reasons: first, | |
1132 * coalescing of multiple updates, and second, because action like | |
1133 * repositioning a tile can require a delay before they affect values | |
1134 * like clientHeight. | |
1135 * @private | |
1136 */ | |
1137 queueUpdateScrollbars_: function() { | |
1138 if (this.scrollbarUpdate_) | |
1139 return; | |
1140 | |
1141 this.scrollbarUpdate_ = window.setTimeout( | |
1142 this.doUpdateScrollbars_.bind(this), 0); | |
1143 }, | |
1144 | |
1145 /** | |
1146 * Does the work of calculating the visibility, height and position of the | |
1147 * scrollbar thumb (there is no track or buttons). | |
1148 * @private | |
1149 */ | |
1150 doUpdateScrollbars_: function() { | |
1151 this.scrollbarUpdate_ = 0; | |
1152 | |
1153 var content = this.content_; | |
1154 | |
1155 // Adjust scroll-height to account for possible header-bar. | |
1156 var adjustedScrollHeight = content.scrollHeight - content.offsetTop; | |
1157 | |
1158 if (adjustedScrollHeight <= content.clientHeight) { | |
1159 this.scrollbar_.hidden = true; | |
1160 return; | |
1161 } else { | |
1162 this.scrollbar_.hidden = false; | |
1163 } | |
1164 | |
1165 var thumbTop = content.offsetTop + | |
1166 content.scrollTop / adjustedScrollHeight * content.clientHeight; | |
1167 var thumbHeight = content.clientHeight / adjustedScrollHeight * | |
1168 this.clientHeight; | |
1169 | |
1170 this.scrollbar_.style.top = thumbTop + 'px'; | |
1171 this.scrollbar_.style.height = thumbHeight + 'px'; | |
1172 this.firePageLayoutEvent_(); | |
1173 }, | |
1174 | |
1175 /** | |
1176 * Get the height for a tile of a certain width. Override this function to | |
1177 * get non-square tiles. | |
1178 * @param {number} width The pixel width of a tile. | |
1179 * @return {number} The height for |width|. | |
1180 */ | |
1181 heightForWidth: function(width) { | |
1182 return width; | |
1183 }, | |
1184 | |
1185 /** Dragging **/ | 908 /** Dragging **/ |
1186 | 909 |
| 910 // TODO(xci) drag |
1187 get isCurrentDragTarget() { | 911 get isCurrentDragTarget() { |
1188 return this.dragWrapper_.isCurrentDragTarget; | 912 return this.dragWrapper_.isCurrentDragTarget; |
1189 }, | 913 }, |
1190 | 914 |
1191 /** | 915 /** |
1192 * Thunk for dragleave events fired on |tileGrid_|. | 916 * Thunk for dragleave events fired on |tileGrid_|. |
1193 * @param {Event} e A MouseEvent for the drag. | 917 * @param {Event} e A MouseEvent for the drag. |
1194 */ | 918 */ |
| 919 // TODO(xci) drag |
1195 doDragLeave: function(e) { | 920 doDragLeave: function(e) { |
1196 this.cleanupDrag(); | 921 this.cleanupDrag(); |
1197 }, | 922 }, |
1198 | 923 |
1199 /** | 924 /** |
1200 * Performs all actions necessary when a drag enters the tile page. | 925 * Performs all actions necessary when a drag enters the tile page. |
1201 * @param {Event} e A mouseover event for the drag enter. | 926 * @param {Event} e A mouseover event for the drag enter. |
1202 */ | 927 */ |
| 928 // TODO(xci) drag |
1203 doDragEnter: function(e) { | 929 doDragEnter: function(e) { |
1204 // Applies the mask so doppleganger tiles disappear into the fog. | 930 // Applies the mask so doppleganger tiles disappear into the fog. |
1205 this.updateMask_(); | 931 this.updateMask_(); |
1206 | 932 |
1207 this.classList.add('animating-tile-page'); | 933 this.classList.add('animating-tile-page'); |
1208 this.withinPageDrag_ = this.contains(currentlyDraggingTile); | 934 this.withinPageDrag_ = this.contains(currentlyDraggingTile); |
1209 this.dragItemIndex_ = this.withinPageDrag_ ? | 935 this.dragItemIndex_ = this.withinPageDrag_ ? |
1210 currentlyDraggingTile.index : this.tileElements_.length; | 936 currentlyDraggingTile.index : this.tileElements_.length; |
1211 this.currentDropIndex_ = this.dragItemIndex_; | 937 this.currentDropIndex_ = this.dragItemIndex_; |
1212 | 938 |
1213 // The new tile may change the number of rows, hence the top margin | 939 // The new tile may change the number of rows, hence the top margin |
1214 // will change. | 940 // will change. |
1215 if (!this.withinPageDrag_) | 941 if (!this.withinPageDrag_) |
| 942 // TODO(xci) this function does nothing now! |
1216 this.updateTopMargin_(); | 943 this.updateTopMargin_(); |
1217 | 944 |
1218 this.doDragOver(e); | 945 this.doDragOver(e); |
1219 }, | 946 }, |
1220 | 947 |
1221 /** | 948 /** |
1222 * Performs all actions necessary when the user moves the cursor during | 949 * Performs all actions necessary when the user moves the cursor during |
1223 * a drag over the tile page. | 950 * a drag over the tile page. |
1224 * @param {Event} e A mouseover event for the drag over. | 951 * @param {Event} e A mouseover event for the drag over. |
1225 */ | 952 */ |
| 953 // TODO(xci) drag |
1226 doDragOver: function(e) { | 954 doDragOver: function(e) { |
1227 e.preventDefault(); | 955 e.preventDefault(); |
1228 | 956 |
1229 this.setDropEffect(e.dataTransfer); | 957 this.setDropEffect(e.dataTransfer); |
1230 var newDragIndex = this.getWouldBeIndexForPoint_(e.pageX, e.pageY); | 958 var newDragIndex = this.getWouldBeIndexForPoint_(e.pageX, e.pageY); |
1231 if (newDragIndex < 0 || newDragIndex >= this.tileElements_.length) | 959 if (newDragIndex < 0 || newDragIndex >= this.tileElements_.length) |
1232 newDragIndex = this.dragItemIndex_; | 960 newDragIndex = this.dragItemIndex_; |
1233 this.updateDropIndicator_(newDragIndex); | 961 this.updateDropIndicator_(newDragIndex); |
1234 }, | 962 }, |
1235 | 963 |
1236 /** | 964 /** |
1237 * Performs all actions necessary when the user completes a drop. | 965 * Performs all actions necessary when the user completes a drop. |
1238 * @param {Event} e A mouseover event for the drag drop. | 966 * @param {Event} e A mouseover event for the drag drop. |
1239 */ | 967 */ |
| 968 // TODO(xci) drag |
1240 doDrop: function(e) { | 969 doDrop: function(e) { |
1241 e.stopPropagation(); | 970 e.stopPropagation(); |
1242 e.preventDefault(); | 971 e.preventDefault(); |
1243 | 972 |
1244 var index = this.currentDropIndex_; | 973 var index = this.currentDropIndex_; |
1245 // Only change data if this was not a 'null drag'. | 974 // Only change data if this was not a 'null drag'. |
1246 if (!((index == this.dragItemIndex_) && this.withinPageDrag_)) { | 975 if (!((index == this.dragItemIndex_) && this.withinPageDrag_)) { |
1247 var adjustedIndex = this.currentDropIndex_ + | 976 var adjustedIndex = this.currentDropIndex_ + |
1248 (index > this.dragItemIndex_ ? 1 : 0); | 977 (index > this.dragItemIndex_ ? 1 : 0); |
1249 if (this.withinPageDrag_) { | 978 if (this.withinPageDrag_) { |
(...skipping 16 matching lines...) Expand all Loading... |
1266 } | 995 } |
1267 | 996 |
1268 this.classList.remove('animating-tile-page'); | 997 this.classList.remove('animating-tile-page'); |
1269 this.cleanupDrag(); | 998 this.cleanupDrag(); |
1270 }, | 999 }, |
1271 | 1000 |
1272 /** | 1001 /** |
1273 * Appends the currently dragged tile to the end of the page. Called | 1002 * Appends the currently dragged tile to the end of the page. Called |
1274 * from outside the page, e.g. when dropping on a nav dot. | 1003 * from outside the page, e.g. when dropping on a nav dot. |
1275 */ | 1004 */ |
| 1005 // TODO(xci) drag |
1276 appendDraggingTile: function() { | 1006 appendDraggingTile: function() { |
1277 var originalPage = currentlyDraggingTile.tilePage; | 1007 var originalPage = currentlyDraggingTile.tilePage; |
1278 if (originalPage == this) | 1008 if (originalPage == this) |
1279 return; | 1009 return; |
1280 | 1010 |
1281 this.addDragData(null, this.tileElements_.length); | 1011 this.addDragData(null, this.tileElements_.length); |
1282 if (originalPage) | 1012 if (originalPage) |
1283 originalPage.cleanupDrag(); | 1013 originalPage.cleanupDrag(); |
1284 }, | 1014 }, |
1285 | 1015 |
1286 /** | 1016 /** |
1287 * Makes sure all the tiles are in the right place after a drag is over. | 1017 * Makes sure all the tiles are in the right place after a drag is over. |
1288 */ | 1018 */ |
| 1019 // TODO(xci) drag |
1289 cleanupDrag: function() { | 1020 cleanupDrag: function() { |
1290 this.repositionTiles_(currentlyDraggingTile); | 1021 this.repositionTiles_(currentlyDraggingTile); |
1291 // Remove the drag mask. | 1022 // Remove the drag mask. |
1292 this.updateMask_(); | 1023 this.updateMask_(); |
1293 }, | 1024 }, |
1294 | 1025 |
1295 /** | 1026 /** |
1296 * Reposition all the tiles (possibly ignoring one). | 1027 * Reposition all the tiles (possibly ignoring one). |
1297 * @param {?Node} ignoreNode An optional node to ignore. | 1028 * @param {?Node} ignoreNode An optional node to ignore. |
1298 * @private | 1029 * @private |
1299 */ | 1030 */ |
| 1031 // TODO(xci) drag |
1300 repositionTiles_: function(ignoreNode) { | 1032 repositionTiles_: function(ignoreNode) { |
| 1033 /* |
1301 for (var i = 0; i < this.tileElements_.length; i++) { | 1034 for (var i = 0; i < this.tileElements_.length; i++) { |
1302 if (!ignoreNode || ignoreNode !== this.tileElements_[i]) | 1035 if (!ignoreNode || ignoreNode !== this.tileElements_[i]) |
1303 this.positionTile_(i); | 1036 ;//this.positionTile_(i); TODO(xci) this function was deleted! |
1304 } | 1037 } |
| 1038 */ |
1305 }, | 1039 }, |
1306 | 1040 |
1307 /** | 1041 /** |
1308 * Updates the visual indicator for the drop location for the active drag. | 1042 * Updates the visual indicator for the drop location for the active drag. |
1309 * @param {Event} e A MouseEvent for the drag. | 1043 * @param {Event} e A MouseEvent for the drag. |
1310 * @private | 1044 * @private |
1311 */ | 1045 */ |
| 1046 // TODO(xci) drag |
1312 updateDropIndicator_: function(newDragIndex) { | 1047 updateDropIndicator_: function(newDragIndex) { |
1313 var oldDragIndex = this.currentDropIndex_; | 1048 var oldDragIndex = this.currentDropIndex_; |
1314 if (newDragIndex == oldDragIndex) | 1049 if (newDragIndex == oldDragIndex) |
1315 return; | 1050 return; |
1316 | 1051 |
1317 var repositionStart = Math.min(newDragIndex, oldDragIndex); | 1052 var repositionStart = Math.min(newDragIndex, oldDragIndex); |
1318 var repositionEnd = Math.max(newDragIndex, oldDragIndex); | 1053 var repositionEnd = Math.max(newDragIndex, oldDragIndex); |
1319 | 1054 |
1320 for (var i = repositionStart; i <= repositionEnd; i++) { | 1055 for (var i = repositionStart; i <= repositionEnd; i++) { |
1321 if (i == this.dragItemIndex_) | 1056 if (i == this.dragItemIndex_) |
1322 continue; | 1057 continue; |
1323 else if (i > this.dragItemIndex_) | 1058 else if (i > this.dragItemIndex_) |
1324 var adjustment = i <= newDragIndex ? -1 : 0; | 1059 var adjustment = i <= newDragIndex ? -1 : 0; |
1325 else | 1060 else |
1326 var adjustment = i >= newDragIndex ? 1 : 0; | 1061 var adjustment = i >= newDragIndex ? 1 : 0; |
1327 | 1062 |
1328 this.positionTile_(i, adjustment); | 1063 //this.positionTile_(i, adjustment); TODO(xci) function was deleted! |
1329 } | 1064 } |
1330 this.currentDropIndex_ = newDragIndex; | 1065 this.currentDropIndex_ = newDragIndex; |
1331 }, | 1066 }, |
1332 | 1067 |
1333 /** | 1068 /** |
1334 * Checks if a page can accept a drag with the given data. | 1069 * Checks if a page can accept a drag with the given data. |
1335 * @param {Event} e The drag event if the drag object. Implementations will | 1070 * @param {Event} e The drag event if the drag object. Implementations will |
1336 * likely want to check |e.dataTransfer|. | 1071 * likely want to check |e.dataTransfer|. |
1337 * @return {boolean} True if this page can handle the drag. | 1072 * @return {boolean} True if this page can handle the drag. |
1338 */ | 1073 */ |
| 1074 // TODO(xci) drag |
1339 shouldAcceptDrag: function(e) { | 1075 shouldAcceptDrag: function(e) { |
1340 return false; | 1076 return false; |
1341 }, | 1077 }, |
1342 | 1078 |
1343 /** | 1079 /** |
1344 * Called to accept a drag drop. Will not be called for in-page drops. | 1080 * Called to accept a drag drop. Will not be called for in-page drops. |
1345 * @param {Object} dataTransfer The data transfer object that holds the drop | 1081 * @param {Object} dataTransfer The data transfer object that holds the drop |
1346 * data. This should only be used if currentlyDraggingTile is null. | 1082 * data. This should only be used if currentlyDraggingTile is null. |
1347 * @param {number} index The tile index at which the drop occurred. | 1083 * @param {number} index The tile index at which the drop occurred. |
1348 */ | 1084 */ |
| 1085 // TODO(xci) drag |
1349 addDragData: function(dataTransfer, index) { | 1086 addDragData: function(dataTransfer, index) { |
1350 assert(false); | 1087 assert(false); |
1351 }, | 1088 }, |
1352 | 1089 |
1353 /** | 1090 /** |
1354 * Called when a tile has been moved (via dragging). Override this to make | 1091 * Called when a tile has been moved (via dragging). Override this to make |
1355 * backend updates. | 1092 * backend updates. |
1356 * @param {Node} draggedTile The tile that was dropped. | 1093 * @param {Node} draggedTile The tile that was dropped. |
1357 * @param {number} prevIndex The previous index of the tile. | 1094 * @param {number} prevIndex The previous index of the tile. |
1358 */ | 1095 */ |
1359 tileMoved: function(draggedTile, prevIndex) { | 1096 tileMoved: function(draggedTile, prevIndex) { |
1360 }, | 1097 }, |
1361 | 1098 |
1362 /** | 1099 /** |
1363 * Sets the drop effect on |dataTransfer| to the desired value (e.g. | 1100 * Sets the drop effect on |dataTransfer| to the desired value (e.g. |
1364 * 'copy'). | 1101 * 'copy'). |
1365 * @param {Object} dataTransfer The drag event dataTransfer object. | 1102 * @param {Object} dataTransfer The drag event dataTransfer object. |
1366 */ | 1103 */ |
| 1104 // TODO(xci) drag |
1367 setDropEffect: function(dataTransfer) { | 1105 setDropEffect: function(dataTransfer) { |
1368 assert(false); | 1106 assert(false); |
1369 }, | 1107 }, |
| 1108 |
| 1109 |
| 1110 // ######################################################################### |
| 1111 // XCI - Extended Chrome Instant |
| 1112 // ######################################################################### |
| 1113 |
| 1114 |
| 1115 // properties |
| 1116 // ------------------------------------------------------------------------- |
| 1117 colCount: 5, |
| 1118 rowCount: 2, |
| 1119 |
| 1120 numOfVisibleRows: 0, |
| 1121 animatingColCount: 5, // TODO how to handle initialization of this value? |
| 1122 |
| 1123 // TODO move to layout? |
| 1124 pageOffset: 0, |
| 1125 |
| 1126 appendTile: function(tile) { |
| 1127 this.tileElements_.push(tile); |
| 1128 this.renderGrid_(); |
| 1129 }, |
| 1130 |
| 1131 // TODO(cxi): if this is not being used, delete. |
| 1132 addTileAt: function(tile, index) { |
| 1133 this.tileElements_.splice(index, 0, tile); |
| 1134 this.appendTile(tile); |
| 1135 }, |
| 1136 |
| 1137 // internal helpers |
| 1138 // ------------------------------------------------------------------------- |
| 1139 |
| 1140 // TODO move to layout? |
| 1141 getNumOfVisibleRows_: function() { |
| 1142 return this.numOfVisibleRows; |
| 1143 }, |
| 1144 |
| 1145 getTileRequiredWidth_: function() { |
| 1146 var grid = this.gridValues_; |
| 1147 return grid.tileWidth + 2 * grid.tileBorderWidth + grid.tileHorMargin; |
| 1148 }, |
| 1149 |
| 1150 getColCountForWidth_: function(width) { |
| 1151 var availableWidth = width + this.gridValues_.tileHorMargin; |
| 1152 var requiredWidth = this.getTileRequiredWidth_(); |
| 1153 var colCount = Math.floor(availableWidth / requiredWidth); |
| 1154 return colCount; |
| 1155 }, |
| 1156 |
| 1157 getWidthForColCount_: function(colCount) { |
| 1158 var requiredWidth = this.getTileRequiredWidth_(); |
| 1159 var width = colCount * requiredWidth - this.gridValues_.tileHorMargin; |
| 1160 return width; |
| 1161 }, |
| 1162 |
| 1163 getBottomPanelWidth_: function() { |
| 1164 var windowWidth = cr.doc.documentElement.clientWidth; |
| 1165 var width; |
| 1166 // TODO(xci): add contants? |
| 1167 if (windowWidth >= 948) { |
| 1168 width = 748; |
| 1169 } else if (windowWidth >= 500) { |
| 1170 width = windowWidth - 2 * this.gridValues_.bottomPanelHorMargin; |
| 1171 } else if (windowWidth >= 300) { |
| 1172 // TODO(pedrosimonetti): check math and document |
| 1173 width = Math.floor(((windowWidth - 300) / 200) * 100 + 200); |
| 1174 } else { |
| 1175 width = 200; |
| 1176 } |
| 1177 return width; |
| 1178 }, |
| 1179 |
| 1180 getAvailableColCount_: function() { |
| 1181 return this.getColCountForWidth_(this.getBottomPanelWidth_()); |
| 1182 }, |
| 1183 |
| 1184 // rendering |
| 1185 // ------------------------------------------------------------------------- |
| 1186 |
| 1187 // TODO(pedrosimonetti): document |
| 1188 renderGrid_: function(colCount) { |
| 1189 colCount = colCount || this.colCount; |
| 1190 |
| 1191 var tileGridContent = this.tileGridContent_; |
| 1192 |
| 1193 var tileElements = this.tileElements_; |
| 1194 |
| 1195 var tileCount = tileElements.length; |
| 1196 var rowCount = Math.ceil(tileCount / colCount); |
| 1197 |
| 1198 var tileRows = tileGridContent.getElementsByClassName('tile-row'); |
| 1199 var tileRow; |
| 1200 var tileRowTiles; |
| 1201 var tileCell; |
| 1202 var tileElement; |
| 1203 var maxColCount; |
| 1204 |
| 1205 var numOfVisibleRows = this.getNumOfVisibleRows_(); |
| 1206 var pageOffset = this.pageOffset; |
| 1207 |
| 1208 for (var tile = 0, row = 0; row < rowCount; row++) { |
| 1209 tileRow = tileRows[row]; |
| 1210 |
| 1211 // Create tile row if there's no one yet. |
| 1212 if (!tileRow) { |
| 1213 tileRow = cr.doc.createElement('div'); |
| 1214 tileRow.className = 'tile-row tile-row-' + row;// TODO do we need id? |
| 1215 tileGridContent.appendChild(tileRow); |
| 1216 } |
| 1217 |
| 1218 // Adjust row visibility. |
| 1219 var rowVisible = (row >= pageOffset && |
| 1220 row <= (pageOffset + numOfVisibleRows - 1)); |
| 1221 this.showTileRow_(tileRow, rowVisible); |
| 1222 |
| 1223 // The tiles inside the current row. |
| 1224 tileRowTiles = tileRow.childNodes; |
| 1225 |
| 1226 // Remove excessive columns from a particular tile row. |
| 1227 maxColCount = Math.min(colCount, tileCount - tile); |
| 1228 while (tileRowTiles.length > maxColCount) { |
| 1229 tileRow.removeChild(tileRow.lastElementChild); |
| 1230 } |
| 1231 |
| 1232 // For each column in the current row. |
| 1233 for (var col = 0; col < colCount; col++, tile++) { |
| 1234 if (tileRowTiles[col]) { |
| 1235 tileCell = tileRowTiles[col]; |
| 1236 } else { |
| 1237 var span = cr.doc.createElement('span'); |
| 1238 tileCell = new TileCell(span, this.gridValues_); |
| 1239 } |
| 1240 |
| 1241 // Reset column class. |
| 1242 this.resetTileCol_(tileCell, col); |
| 1243 |
| 1244 // Render Tiles. |
| 1245 if (tile < tileCount) { |
| 1246 tileCell.classList.remove('filler'); |
| 1247 tileElement = tileElements[tile]; |
| 1248 if (tileCell.firstChild) { |
| 1249 if (tileElement != tileCell.firstChild) { |
| 1250 tileCell.replaceChild(tileElement, tileCell.firstChild); |
| 1251 } |
| 1252 } else { |
| 1253 tileCell.appendChild(tileElement); |
| 1254 } |
| 1255 } else { |
| 1256 // TODO(xci): check how to solve the problem with filler + animation |
| 1257 if (!tileCell.classList.contains('filler')) { |
| 1258 tileCell.classList.add('filler'); |
| 1259 tileElement = cr.doc.createElement('span'); |
| 1260 if (tileCell.firstChild) |
| 1261 tileCell.replaceChild(tileElement, tileCell.firstChild); |
| 1262 else |
| 1263 tileCell.appendChild(tileElement); |
| 1264 } |
| 1265 } |
| 1266 |
| 1267 if (!tileRowTiles[col]) { |
| 1268 tileRow.appendChild(tileCell); |
| 1269 } |
| 1270 } |
| 1271 } |
| 1272 |
| 1273 // Remove excessive tile rows from the tile grid. |
| 1274 while (tileRows.length > rowCount) { |
| 1275 tileGridContent.removeChild(tileGridContent.lastElementChild); |
| 1276 } |
| 1277 |
| 1278 this.colCount = colCount; |
| 1279 this.rowCount = rowCount; |
| 1280 }, |
| 1281 |
| 1282 layout_: function() { |
| 1283 var pageList = $('page-list'); |
| 1284 var panel = this.content_; |
| 1285 var menu = $('page-list-menu'); |
| 1286 var tileGrid = this.tileGrid_; |
| 1287 var tileGridContent = this.tileGridContent_; |
| 1288 var tileRows = tileGridContent.getElementsByClassName('tile-row'); |
| 1289 |
| 1290 tileGridContent.classList.add('animate-tile'); |
| 1291 |
| 1292 var bottomPanelWidth = this.getBottomPanelWidth_(); |
| 1293 var colCount = this.getColCountForWidth_(bottomPanelWidth); |
| 1294 var lastColCount = this.colCount; |
| 1295 var animatingColCount = this.animatingColCount; |
| 1296 |
| 1297 var windowHeight = cr.doc.documentElement.clientHeight; |
| 1298 |
| 1299 // TODO better handling of height state |
| 1300 // changeVisibleRows |
| 1301 // TODO need to call paginate when height changes |
| 1302 var numOfVisibleRows = this.numOfVisibleRows; |
| 1303 // TODO(xci) constants? |
| 1304 if (windowHeight > 500 && this.tileElements_.length > 5) { |
| 1305 numOfVisibleRows = 2; |
| 1306 } else { |
| 1307 numOfVisibleRows = 1; |
| 1308 } |
| 1309 |
| 1310 if (numOfVisibleRows != this.numOfVisibleRows) { |
| 1311 this.numOfVisibleRows = numOfVisibleRows; |
| 1312 this.paginate_(null, true); |
| 1313 pageList.style.height = (107 * numOfVisibleRows) + 'px'; |
| 1314 //tileGrid.style.height = (107 * numOfVisibleRows) + 'px'; |
| 1315 } |
| 1316 |
| 1317 // changeVisibleCols |
| 1318 if (colCount != animatingColCount) { |
| 1319 |
| 1320 var newWidth = this.getWidthForColCount_(colCount); |
| 1321 if (colCount > animatingColCount) { |
| 1322 |
| 1323 // TODO actual size check |
| 1324 if (colCount != lastColCount) { |
| 1325 this.renderGrid_(colCount); |
| 1326 } |
| 1327 |
| 1328 this.showTileCols_(animatingColCount, false); |
| 1329 |
| 1330 // TODO(xci) fix the slowdown debug function and test if we really |
| 1331 // need this closure and if we really need to pass animatingCount. |
| 1332 var self = this; |
| 1333 setTimeout((function(animatingColCount) { |
| 1334 return function() { |
| 1335 self.showTileCols_(animatingColCount, true); |
| 1336 } |
| 1337 })(animatingColCount), 0); |
| 1338 |
| 1339 } else { |
| 1340 this.showTileCols_(colCount, false); |
| 1341 } |
| 1342 |
| 1343 tileGrid.style.width = newWidth + 'px'; |
| 1344 menu.style.width = newWidth + 'px'; |
| 1345 |
| 1346 // TODO: listen animateEnd |
| 1347 var self = this; |
| 1348 this.onTileGridAnimationEndHandler_ = function() { |
| 1349 if (colCount < lastColCount) { |
| 1350 self.renderGrid_(colCount); |
| 1351 } else { |
| 1352 self.showTileCols_(0, true); |
| 1353 } |
| 1354 }; |
| 1355 |
| 1356 this.paginate_(); |
| 1357 } |
| 1358 |
| 1359 panel.style.width = bottomPanelWidth + 'px'; |
| 1360 |
| 1361 this.animatingColCount = colCount; |
| 1362 }, |
| 1363 |
| 1364 // animation helpers |
| 1365 // ------------------------------------------------------------------------- |
| 1366 |
| 1367 // TODO make it local? |
| 1368 showTileRow_: function(row, show) { |
| 1369 row.classList[show ? 'remove' : 'add']('hide-row'); |
| 1370 }, |
| 1371 |
| 1372 // TODO make it local? |
| 1373 showTileCols_: function(col, show) { |
| 1374 var prop = show ? 'remove' : 'add'; |
| 1375 var max = 10; // TODO(pedrosimonetti) const? |
| 1376 var tileGridContent = this.tileGridContent_; |
| 1377 for (var i = col; i < max; i++) { |
| 1378 tileGridContent.classList[prop]('hide-col-' + i); |
| 1379 } |
| 1380 }, |
| 1381 |
| 1382 // TODO make it local? |
| 1383 resetTileCol_: function(tileCell, col) { |
| 1384 var max = 10; |
| 1385 for (var i = 0; i < max; i++) { |
| 1386 if (i != col) { |
| 1387 tileCell.classList.remove('tile-col-' + i); |
| 1388 } |
| 1389 } |
| 1390 tileCell.classList.add('tile-col-' + col); |
| 1391 }, |
| 1392 |
| 1393 // pagination |
| 1394 // ------------------------------------------------------------------------- |
| 1395 |
| 1396 paginate_: function(pageOffset, force) { |
| 1397 var numOfVisibleRows = this.getNumOfVisibleRows_(); |
| 1398 var pageOffset = typeof pageOffset == 'number' ? |
| 1399 pageOffset : this.pageOffset; |
| 1400 |
| 1401 pageOffset = Math.max(0, pageOffset); |
| 1402 pageOffset = Math.min(this.rowCount - numOfVisibleRows, pageOffset); |
| 1403 |
| 1404 if (pageOffset != this.pageOffset || force) { |
| 1405 var rows = this.tileGridContent_.getElementsByClassName('tile-row'); |
| 1406 for (var i = 0, l = rows.length; i < l; i++) { |
| 1407 var row = rows[i]; |
| 1408 if (i >= pageOffset && i <= (pageOffset + numOfVisibleRows - 1)) { |
| 1409 this.showTileRow_(row, true); |
| 1410 } else { |
| 1411 this.showTileRow_(row, false); |
| 1412 } |
| 1413 } |
| 1414 |
| 1415 this.pageOffset = pageOffset; |
| 1416 this.tileGridContent_.style.webkitTransform = 'translate3d(0, ' + |
| 1417 (-pageOffset * 106) + 'px, 0)'; |
| 1418 } |
| 1419 }, |
| 1420 |
| 1421 // event handlers |
| 1422 // ------------------------------------------------------------------------- |
| 1423 |
| 1424 onResize_: function() { |
| 1425 this.layout_(); |
| 1426 }, |
| 1427 |
| 1428 onTileGridAnimationEnd_: function() { |
| 1429 // TODO(xci): figure out how to cleanup each kind animation properly |
| 1430 if (event.target == this.tileGrid_ && |
| 1431 this.onTileGridAnimationEndHandler_) { |
| 1432 if (this.tileGridContent_.classList.contains('animate-tile')) { |
| 1433 this.onTileGridAnimationEndHandler_(); |
| 1434 //this.tileGridContent_.classList.remove('animate-tile') |
| 1435 } |
| 1436 } |
| 1437 }, |
| 1438 |
| 1439 onKeyUp_: function(e) { |
| 1440 var pageOffset = this.pageOffset; |
| 1441 |
| 1442 var keyCode = e.keyCode; |
| 1443 if (keyCode == 40 /* down */) { |
| 1444 pageOffset++; |
| 1445 } else if (keyCode == 38 /* up */) { |
| 1446 pageOffset--; |
| 1447 } |
| 1448 |
| 1449 if (pageOffset != this.pageOffset) { |
| 1450 this.paginate_(pageOffset); |
| 1451 } |
| 1452 } |
1370 }; | 1453 }; |
1371 | 1454 |
| 1455 |
| 1456 var duration = 200; |
| 1457 var defaultDuration = 200; |
| 1458 var animatedProperties = [ |
| 1459 '#card-slider-frame .tile-grid', |
| 1460 '#card-slider-frame .tile-grid-content', |
| 1461 '#card-slider-frame .tile-row', |
| 1462 '.animate-tile .tile-cell', |
| 1463 '.debug .animate-tile .tile-cell' |
| 1464 ]; |
| 1465 |
| 1466 function adjustAnimationTiming(slownessFactor, selectors) { |
| 1467 slownessFactor = slownessFactor || 1; |
| 1468 selectors = selectors || animatedProperties; |
| 1469 duration = defaultDuration * slownessFactor; |
| 1470 for (var i = 0, l = selectors.length; i < l; i++) { |
| 1471 var selector = selectors[i]; |
| 1472 var rule = findCSSRule(selector); |
| 1473 if (rule) { |
| 1474 rule.style.webkitTransitionDuration = duration + 'ms'; |
| 1475 } else { |
| 1476 throw 'Could not find the CSS rule "' + selector + '"'; |
| 1477 } |
| 1478 } |
| 1479 } |
| 1480 |
| 1481 function findCSSRule(selectorText) { |
| 1482 var rules = document.styleSheets[0].rules; |
| 1483 for (var i = 0, l = rules.length; i < l; i++) { |
| 1484 var rule = rules[i]; |
| 1485 if (rule.selectorText == selectorText) |
| 1486 { |
| 1487 return rule; |
| 1488 } |
| 1489 } |
| 1490 } |
| 1491 |
| 1492 function changeSlowFactor(el) { |
| 1493 if (el.value) |
| 1494 adjustAnimationTiming(el.value - 0, animatedProperties); |
| 1495 } |
| 1496 |
| 1497 function changeDebugMode(el) { |
| 1498 var prop = el.checked ? 'add' : 'remove'; |
| 1499 document.body.classList[prop]('debug'); |
| 1500 } |
| 1501 |
1372 return { | 1502 return { |
1373 getCurrentlyDraggingTile: getCurrentlyDraggingTile, | 1503 // TODO(xci) drag |
1374 setCurrentDropEffect: setCurrentDropEffect, | 1504 //getCurrentlyDraggingTile2: getCurrentlyDraggingTile, |
1375 TilePage: TilePage, | 1505 //setCurrentDropEffect2: setCurrentDropEffect, |
| 1506 Tile2: Tile, |
| 1507 TilePage2: TilePage, |
1376 }; | 1508 }; |
1377 }); | 1509 }); |
OLD | NEW |