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 /** | 5 /** |
6 * @param {Element} container Content container. | 6 * @param {Element} container Content container. |
7 * @param {cr.ui.ArrayDataModel} dataModel Data model. | 7 * @param {cr.ui.ArrayDataModel} dataModel Data model. |
8 * @param {cr.ui.ListSelectionModel} selectionModel Selection model. | 8 * @param {cr.ui.ListSelectionModel} selectionModel Selection model. |
9 * @param {MetadataCache} metadataCache Metadata cache. | 9 * @param {MetadataCache} metadataCache Metadata cache. |
10 * @param {function} toggleMode Function to switch to the Slide mode. | 10 * @param {function} toggleMode Function to switch to the Slide mode. |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
82 * Inherit from HTMLDivElement. | 82 * Inherit from HTMLDivElement. |
83 */ | 83 */ |
84 Mosaic.prototype.__proto__ = HTMLDivElement.prototype; | 84 Mosaic.prototype.__proto__ = HTMLDivElement.prototype; |
85 | 85 |
86 /** | 86 /** |
87 * Default layout delay in ms. | 87 * Default layout delay in ms. |
88 */ | 88 */ |
89 Mosaic.LAYOUT_DELAY = 200; | 89 Mosaic.LAYOUT_DELAY = 200; |
90 | 90 |
91 /** | 91 /** |
92 * Smooth scroll animation duration when scrolling using keyboard or | |
93 * clicking on a partly visible tile. In ms. | |
94 */ | |
95 Mosaic.ANIMATED_SCROLL_DURATION = 500; | |
96 | |
97 /** | |
92 * Decorate a Mosaic instance. | 98 * Decorate a Mosaic instance. |
93 * | 99 * |
94 * @param {Mosaic} self Self pointer. | 100 * @param {Mosaic} self Self pointer. |
95 * @param {cr.ui.ArrayDataModel} dataModel Data model. | 101 * @param {cr.ui.ArrayDataModel} dataModel Data model. |
96 * @param {cr.ui.ListSelectionModel} selectionModel Selection model. | 102 * @param {cr.ui.ListSelectionModel} selectionModel Selection model. |
97 * @param {MetadataCache} metadataCache Metadata cache. | 103 * @param {MetadataCache} metadataCache Metadata cache. |
98 * @param {function(string)} onThumbnailError Thumbnail load error handler. | 104 * @param {function(string)} onThumbnailError Thumbnail load error handler. |
99 */ | 105 */ |
100 Mosaic.decorate = function(self, dataModel, selectionModel, metadataCache, | 106 Mosaic.decorate = function(self, dataModel, selectionModel, metadataCache, |
101 onThumbnailError) { | 107 onThumbnailError) { |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
163 | 169 |
164 this.selectionModel_.addEventListener('change', this.onSelection_.bind(this)); | 170 this.selectionModel_.addEventListener('change', this.onSelection_.bind(this)); |
165 this.selectionModel_.addEventListener('leadIndexChange', | 171 this.selectionModel_.addEventListener('leadIndexChange', |
166 this.onLeadChange_.bind(this)); | 172 this.onLeadChange_.bind(this)); |
167 | 173 |
168 this.dataModel_.addEventListener('splice', this.onSplice_.bind(this)); | 174 this.dataModel_.addEventListener('splice', this.onSplice_.bind(this)); |
169 this.dataModel_.addEventListener('content', this.onContentChange_.bind(this)); | 175 this.dataModel_.addEventListener('content', this.onContentChange_.bind(this)); |
170 }; | 176 }; |
171 | 177 |
172 /** | 178 /** |
179 * Smoothly scrolls the container to the specified position using | |
180 * f(x) = sqrt(x) speed function normalized to animation duration. | |
181 * @param {number} targetPosition Horizontal scroll position in pixels. | |
182 */ | |
183 Mosaic.prototype.animatedScrollTo = function(targetPosition) { | |
184 if (this.scrollTimer_) | |
185 clearInterval(this.scrollTimer_); | |
yoshiki
2013/02/12 01:30:52
Assign null or undefined after clearing the interv
mtomasz
2013/02/12 04:19:21
Done.
| |
186 | |
187 // Mouse move events are fired without touching the mouse because of scrolling | |
188 // the container. Therefore, these events have to be suppressed. | |
189 this.suppressHovering_ = true; | |
190 | |
191 // Calculates integral area from t1 to t2 of f(x) = sqrt(x) dx. | |
192 var integral = function(t1, t2) { | |
193 return 2.0 / 3.0 * Math.pow(t2, 3.0 / 2.0) - | |
194 2.0 / 3.0 * Math.pow(t1, 3.0 / 2.0); | |
195 }; | |
196 | |
197 var delta = targetPosition - this.scrollLeft; | |
198 var factor = delta / integral(0, Mosaic.ANIMATED_SCROLL_DURATION); | |
199 var startTime = Date.now(); | |
200 var lastPosition = 0; | |
201 | |
202 this.scrollTimer_ = setInterval(function() { | |
yoshiki
2013/02/12 01:30:52
It is better to use "requestAnimationFrame" on ani
mtomasz
2013/02/12 04:19:21
This is great, I didn't know about it! Done.
| |
203 var position = Date.now() - startTime; | |
204 var step = factor * | |
205 integral(Math.max(0, Mosaic.ANIMATED_SCROLL_DURATION - position), | |
206 Math.max(0, Mosaic.ANIMATED_SCROLL_DURATION - lastPosition)); | |
207 | |
208 var lastScrollLeft = this.scrollLeft; | |
209 console.log(step); | |
yoshiki
2013/02/12 01:30:52
Remove?
mtomasz
2013/02/12 04:19:21
Done.
| |
210 this.scrollLeft += step; | |
211 | |
212 if (lastScrollLeft == this.scrollLeft) { | |
yoshiki
2013/02/12 01:30:52
We can replace this condition with just "step == 0
mtomasz
2013/02/12 04:19:21
Nope, this didn't work when reaching edges, eg. wh
| |
213 clearInterval(this.scrollTimer_); | |
214 this.scrollTimer_ = null; | |
215 | |
216 // Release the hovering lock after a safe delay to avoid hovering | |
217 // a tile because of altering |this.scrollLeft|. | |
218 setTimeout(function() { | |
219 if (!this.scrollTimer_) | |
220 this.suppressHovering_ = false; | |
221 }.bind(this), 100); | |
222 } | |
223 | |
224 lastPosition = position; | |
225 }.bind(this), 10); | |
226 }; | |
227 | |
228 /** | |
173 * @return {Mosaic.Tile} Selected tile or undefined if no selection. | 229 * @return {Mosaic.Tile} Selected tile or undefined if no selection. |
174 */ | 230 */ |
175 Mosaic.prototype.getSelectedTile = function() { | 231 Mosaic.prototype.getSelectedTile = function() { |
176 return this.tiles_ && this.tiles_[this.selectionModel_.selectedIndex]; | 232 return this.tiles_ && this.tiles_[this.selectionModel_.selectedIndex]; |
177 }; | 233 }; |
178 | 234 |
179 /** | 235 /** |
180 * @param {number} index Tile index. | 236 * @param {number} index Tile index. |
181 * @return {Rect} Tile's image rectangle. | 237 * @return {Rect} Tile's image rectangle. |
182 */ | 238 */ |
183 Mosaic.prototype.getTileRect = function(index) { | 239 Mosaic.prototype.getTileRect = function(index) { |
184 var tile = this.tiles_[index]; | 240 var tile = this.tiles_[index]; |
185 return tile && tile.getImageRect(); | 241 return tile && tile.getImageRect(); |
186 }; | 242 }; |
187 | 243 |
188 /** | 244 /** |
189 * @param {number} index Tile index. | 245 * @param {number} index Tile index. |
190 * Scroll the given tile into the viewport. | 246 * Scroll the given tile into the viewport. |
191 */ | 247 */ |
192 Mosaic.prototype.scrollIntoView = function(index) { | 248 Mosaic.prototype.scrollIntoView = function(index) { |
193 var tile = this.tiles_[index]; | 249 var tile = this.tiles_[index]; |
194 if (tile) tile.scrollIntoView(); | 250 if (tile) tile.scrollIntoView(); |
yoshiki
2013/02/12 01:30:52
Add the boolean argument |opt_animated|.
mtomasz
2013/02/12 04:19:21
Can you clarify? This argument is optional.
yoshiki
2013/02/12 04:49:10
Sorry, it was my mistake. It's optional.
On 2013/
| |
195 }; | 251 }; |
196 | 252 |
197 /** | 253 /** |
198 * Load multiple tiles. | 254 * Load multiple tiles. |
199 * | 255 * |
200 * @param {Array.<Mosaic.Tile>} tiles Array of tiles. | 256 * @param {Array.<Mosaic.Tile>} tiles Array of tiles. |
201 * @param {function} opt_callback Completion callback. | 257 * @param {function} opt_callback Completion callback. |
202 * @private | 258 * @private |
203 */ | 259 */ |
204 Mosaic.prototype.loadTiles_ = function(tiles, opt_callback) { | 260 Mosaic.prototype.loadTiles_ = function(tiles, opt_callback) { |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
305 }; | 361 }; |
306 | 362 |
307 /** | 363 /** |
308 * Mouse event handler. | 364 * Mouse event handler. |
309 * | 365 * |
310 * @param {Event} event Event. | 366 * @param {Event} event Event. |
311 * @private | 367 * @private |
312 */ | 368 */ |
313 Mosaic.prototype.onMouseEvent_ = function(event) { | 369 Mosaic.prototype.onMouseEvent_ = function(event) { |
314 // Navigating with mouse, enable hover state. | 370 // Navigating with mouse, enable hover state. |
315 this.classList.add('hover-visible'); | 371 if (!this.suppressHovering_) |
372 this.classList.add('hover-visible'); | |
316 | 373 |
317 if (event.type == 'mousemove') | 374 if (event.type == 'mousemove') |
318 return; | 375 return; |
319 | 376 |
320 var index = -1; | 377 var index = -1; |
321 for (var target = event.target; | 378 for (var target = event.target; |
322 target && (target != this); | 379 target && (target != this); |
323 target = target.parentNode) { | 380 target = target.parentNode) { |
324 if (target.classList.contains('mosaic-tile')) { | 381 if (target.classList.contains('mosaic-tile')) { |
325 index = this.dataModel_.indexOf(target.getItem()); | 382 index = this.dataModel_.indexOf(target.getItem()); |
(...skipping 20 matching lines...) Expand all Loading... | |
346 /** | 403 /** |
347 * Lead item change handler. | 404 * Lead item change handler. |
348 * | 405 * |
349 * @param {Event} event Event. | 406 * @param {Event} event Event. |
350 * @private | 407 * @private |
351 */ | 408 */ |
352 Mosaic.prototype.onLeadChange_ = function(event) { | 409 Mosaic.prototype.onLeadChange_ = function(event) { |
353 var index = event.newValue; | 410 var index = event.newValue; |
354 if (index >= 0) { | 411 if (index >= 0) { |
355 var tile = this.tiles_[index]; | 412 var tile = this.tiles_[index]; |
356 if (tile) tile.scrollIntoView(); | 413 if (tile) tile.scrollIntoView(); |
yoshiki
2013/02/12 01:30:52
Add the boolean argument |opt_animated|.
mtomasz
2013/02/12 04:19:21
As above, please clarify.
| |
357 } | 414 } |
358 }; | 415 }; |
359 | 416 |
360 /** | 417 /** |
361 * Splice event handler. | 418 * Splice event handler. |
362 * | 419 * |
363 * @param {Event} event Event. | 420 * @param {Event} event Event. |
364 * @private | 421 * @private |
365 */ | 422 */ |
366 Mosaic.prototype.onSplice_ = function(event) { | 423 Mosaic.prototype.onSplice_ = function(event) { |
(...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
563 */ | 620 */ |
564 Mosaic.Layout.PADDING_BOTTOM = 70; | 621 Mosaic.Layout.PADDING_BOTTOM = 70; |
565 | 622 |
566 /** | 623 /** |
567 * Horizontal and vertical spacing between images. Should be kept in sync | 624 * Horizontal and vertical spacing between images. Should be kept in sync |
568 * with the style of .mosaic-item in gallery.css (= 2 * ( 4 + 1)) | 625 * with the style of .mosaic-item in gallery.css (= 2 * ( 4 + 1)) |
569 */ | 626 */ |
570 Mosaic.Layout.SPACING = 10; | 627 Mosaic.Layout.SPACING = 10; |
571 | 628 |
572 /** | 629 /** |
630 * Margin for scrolling using keyboard. Distance between a selected tile | |
631 * and window border. | |
632 */ | |
633 Mosaic.Layout.SCROLL_MARGIN = 30; | |
634 | |
635 /** | |
573 * Layout mode: commit to DOM immediately. | 636 * Layout mode: commit to DOM immediately. |
574 */ | 637 */ |
575 Mosaic.Layout.MODE_FINAL = 'final'; | 638 Mosaic.Layout.MODE_FINAL = 'final'; |
576 | 639 |
577 /** | 640 /** |
578 * Layout mode: do not commit layout to DOM until it is complete or the viewport | 641 * Layout mode: do not commit layout to DOM until it is complete or the viewport |
579 * overflows. | 642 * overflows. |
580 */ | 643 */ |
581 Mosaic.Layout.MODE_TENTATIVE = 'tentative'; | 644 Mosaic.Layout.MODE_TENTATIVE = 'tentative'; |
582 | 645 |
(...skipping 1013 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1596 this.style.top = top + 'px'; | 1659 this.style.top = top + 'px'; |
1597 this.style.width = width + 'px'; | 1660 this.style.width = width + 'px'; |
1598 this.style.height = height + 'px'; | 1661 this.style.height = height + 'px'; |
1599 | 1662 |
1600 if (!this.wrapper_) { // First time, create DOM. | 1663 if (!this.wrapper_) { // First time, create DOM. |
1601 this.container_.appendChild(this); | 1664 this.container_.appendChild(this); |
1602 var border = util.createChild(this, 'img-border'); | 1665 var border = util.createChild(this, 'img-border'); |
1603 this.wrapper_ = util.createChild(border, 'img-wrapper'); | 1666 this.wrapper_ = util.createChild(border, 'img-wrapper'); |
1604 } | 1667 } |
1605 if (this.hasAttribute('selected')) | 1668 if (this.hasAttribute('selected')) |
1606 this.scrollIntoView(); | 1669 this.scrollIntoView(false); |
1607 | 1670 |
1608 this.thumbnailLoader_.attachImage(this.wrapper_, | 1671 this.thumbnailLoader_.attachImage(this.wrapper_, |
1609 ThumbnailLoader.FillMode.FILL); | 1672 ThumbnailLoader.FillMode.FILL); |
1610 }; | 1673 }; |
1611 | 1674 |
1612 /** | 1675 /** |
1613 * If the tile is not fully visible scroll the parent to make it fully visible. | 1676 * If the tile is not fully visible scroll the parent to make it fully visible. |
1677 * @param {boolean} opt_animated True, if scroll should be animated, | |
1678 * default: true. | |
1614 */ | 1679 */ |
1615 Mosaic.Tile.prototype.scrollIntoView = function() { | 1680 Mosaic.Tile.prototype.scrollIntoView = function(opt_animated) { |
1616 if (this.left_ == null) // Not laid out. | 1681 if (this.left_ == null) // Not laid out. |
1617 return; | 1682 return; |
1618 | 1683 |
1619 if (this.left_ < this.container_.scrollLeft) { | 1684 var targetPosition; |
1620 this.container_.scrollLeft = this.left_; | 1685 var tileLeft = this.left_ - Mosaic.Layout.SCROLL_MARGIN; |
1686 if (tileLeft < this.container_.scrollLeft) { | |
1687 targetPosition = tileLeft; | |
1621 } else { | 1688 } else { |
1622 var tileRight = this.left_ + this.width_; | 1689 var tileRight = this.left_ + this.width_ + Mosaic.Layout.SCROLL_MARGIN; |
1623 var scrollRight = this.container_.scrollLeft + this.container_.clientWidth; | 1690 var scrollRight = this.container_.scrollLeft + this.container_.clientWidth; |
1624 if (tileRight > scrollRight) | 1691 if (tileRight > scrollRight) |
1625 this.container_.scrollLeft = tileRight - this.container_.clientWidth; | 1692 targetPosition = tileRight - this.container_.clientWidth; |
1693 } | |
1694 | |
1695 if (targetPosition) { | |
1696 if (opt_animated === false) | |
1697 this.container_.scrollLeft = targetPosition; | |
1698 else | |
1699 this.container_.animatedScrollTo(targetPosition); | |
1626 } | 1700 } |
1627 }; | 1701 }; |
1628 | 1702 |
1629 /** | 1703 /** |
1630 * @return {Rect} Rectangle occupied by the tile's image, | 1704 * @return {Rect} Rectangle occupied by the tile's image, |
1631 * relative to the viewport. | 1705 * relative to the viewport. |
1632 */ | 1706 */ |
1633 Mosaic.Tile.prototype.getImageRect = function() { | 1707 Mosaic.Tile.prototype.getImageRect = function() { |
1634 if (this.left_ == null) // Not laid out. | 1708 if (this.left_ == null) // Not laid out. |
1635 return null; | 1709 return null; |
1636 | 1710 |
1637 var margin = Mosaic.Layout.SPACING / 2; | 1711 var margin = Mosaic.Layout.SPACING / 2; |
1638 return new Rect(this.left_ - this.container_.scrollLeft, this.top_, | 1712 return new Rect(this.left_ - this.container_.scrollLeft, this.top_, |
1639 this.width_, this.height_).inflate(-margin, -margin); | 1713 this.width_, this.height_).inflate(-margin, -margin); |
1640 }; | 1714 }; |
1641 | 1715 |
1642 /** | 1716 /** |
1643 * @return {number} X coordinate of the tile center. | 1717 * @return {number} X coordinate of the tile center. |
1644 */ | 1718 */ |
1645 Mosaic.Tile.prototype.getCenterX = function() { | 1719 Mosaic.Tile.prototype.getCenterX = function() { |
1646 return this.left_ + Math.round(this.width_ / 2); | 1720 return this.left_ + Math.round(this.width_ / 2); |
1647 }; | 1721 }; |
OLD | NEW |