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.scrollAnimation_) { |
| 185 webkitCancelAnimationFrame(this.scrollAnimation_); |
| 186 this.scrollAnimation_ = null; |
| 187 } |
| 188 |
| 189 // Mouse move events are fired without touching the mouse because of scrolling |
| 190 // the container. Therefore, these events have to be suppressed. |
| 191 this.suppressHovering_ = true; |
| 192 |
| 193 // Calculates integral area from t1 to t2 of f(x) = sqrt(x) dx. |
| 194 var integral = function(t1, t2) { |
| 195 return 2.0 / 3.0 * Math.pow(t2, 3.0 / 2.0) - |
| 196 2.0 / 3.0 * Math.pow(t1, 3.0 / 2.0); |
| 197 }; |
| 198 |
| 199 var delta = targetPosition - this.scrollLeft; |
| 200 var factor = delta / integral(0, Mosaic.ANIMATED_SCROLL_DURATION); |
| 201 var startTime = Date.now(); |
| 202 var lastPosition = 0; |
| 203 var scrollOffset = this.scrollLeft; |
| 204 |
| 205 var animationFrame = function() { |
| 206 var position = Date.now() - startTime; |
| 207 var step = factor * |
| 208 integral(Math.max(0, Mosaic.ANIMATED_SCROLL_DURATION - position), |
| 209 Math.max(0, Mosaic.ANIMATED_SCROLL_DURATION - lastPosition)); |
| 210 scrollOffset += step; |
| 211 |
| 212 var oldScrollLeft = this.scrollLeft; |
| 213 var newScrollLeft = Math.round(scrollOffset); |
| 214 |
| 215 if (oldScrollLeft != newScrollLeft) |
| 216 this.scrollLeft = newScrollLeft; |
| 217 |
| 218 if (step == 0 || this.scrollLeft != newScrollLeft) { |
| 219 this.scrollAnimation_ = null; |
| 220 // Release the hovering lock after a safe delay to avoid hovering |
| 221 // a tile because of altering |this.scrollLeft|. |
| 222 setTimeout(function() { |
| 223 if (!this.scrollAnimation_) |
| 224 this.suppressHovering_ = false; |
| 225 }.bind(this), 100); |
| 226 } else { |
| 227 // Continue the animation. |
| 228 this.scrollAnimation_ = requestAnimationFrame(animationFrame); |
| 229 } |
| 230 |
| 231 lastPosition = position; |
| 232 }.bind(this); |
| 233 |
| 234 // Start the animation. |
| 235 this.scrollAnimation_ = requestAnimationFrame(animationFrame); |
| 236 }; |
| 237 |
| 238 /** |
173 * @return {Mosaic.Tile} Selected tile or undefined if no selection. | 239 * @return {Mosaic.Tile} Selected tile or undefined if no selection. |
174 */ | 240 */ |
175 Mosaic.prototype.getSelectedTile = function() { | 241 Mosaic.prototype.getSelectedTile = function() { |
176 return this.tiles_ && this.tiles_[this.selectionModel_.selectedIndex]; | 242 return this.tiles_ && this.tiles_[this.selectionModel_.selectedIndex]; |
177 }; | 243 }; |
178 | 244 |
179 /** | 245 /** |
180 * @param {number} index Tile index. | 246 * @param {number} index Tile index. |
181 * @return {Rect} Tile's image rectangle. | 247 * @return {Rect} Tile's image rectangle. |
182 */ | 248 */ |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
305 }; | 371 }; |
306 | 372 |
307 /** | 373 /** |
308 * Mouse event handler. | 374 * Mouse event handler. |
309 * | 375 * |
310 * @param {Event} event Event. | 376 * @param {Event} event Event. |
311 * @private | 377 * @private |
312 */ | 378 */ |
313 Mosaic.prototype.onMouseEvent_ = function(event) { | 379 Mosaic.prototype.onMouseEvent_ = function(event) { |
314 // Navigating with mouse, enable hover state. | 380 // Navigating with mouse, enable hover state. |
315 this.classList.add('hover-visible'); | 381 if (!this.suppressHovering_) |
| 382 this.classList.add('hover-visible'); |
316 | 383 |
317 if (event.type == 'mousemove') | 384 if (event.type == 'mousemove') |
318 return; | 385 return; |
319 | 386 |
320 var index = -1; | 387 var index = -1; |
321 for (var target = event.target; | 388 for (var target = event.target; |
322 target && (target != this); | 389 target && (target != this); |
323 target = target.parentNode) { | 390 target = target.parentNode) { |
324 if (target.classList.contains('mosaic-tile')) { | 391 if (target.classList.contains('mosaic-tile')) { |
325 index = this.dataModel_.indexOf(target.getItem()); | 392 index = this.dataModel_.indexOf(target.getItem()); |
(...skipping 236 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
562 */ | 629 */ |
563 Mosaic.Layout.PADDING_BOTTOM = 50; | 630 Mosaic.Layout.PADDING_BOTTOM = 50; |
564 | 631 |
565 /** | 632 /** |
566 * Horizontal and vertical spacing between images. Should be kept in sync | 633 * Horizontal and vertical spacing between images. Should be kept in sync |
567 * with the style of .mosaic-item in gallery.css (= 2 * ( 4 + 1)) | 634 * with the style of .mosaic-item in gallery.css (= 2 * ( 4 + 1)) |
568 */ | 635 */ |
569 Mosaic.Layout.SPACING = 10; | 636 Mosaic.Layout.SPACING = 10; |
570 | 637 |
571 /** | 638 /** |
| 639 * Margin for scrolling using keyboard. Distance between a selected tile |
| 640 * and window border. |
| 641 */ |
| 642 Mosaic.Layout.SCROLL_MARGIN = 30; |
| 643 |
| 644 /** |
572 * Layout mode: commit to DOM immediately. | 645 * Layout mode: commit to DOM immediately. |
573 */ | 646 */ |
574 Mosaic.Layout.MODE_FINAL = 'final'; | 647 Mosaic.Layout.MODE_FINAL = 'final'; |
575 | 648 |
576 /** | 649 /** |
577 * Layout mode: do not commit layout to DOM until it is complete or the viewport | 650 * Layout mode: do not commit layout to DOM until it is complete or the viewport |
578 * overflows. | 651 * overflows. |
579 */ | 652 */ |
580 Mosaic.Layout.MODE_TENTATIVE = 'tentative'; | 653 Mosaic.Layout.MODE_TENTATIVE = 'tentative'; |
581 | 654 |
(...skipping 1014 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1596 this.style.top = top + 'px'; | 1669 this.style.top = top + 'px'; |
1597 this.style.width = width + 'px'; | 1670 this.style.width = width + 'px'; |
1598 this.style.height = height + 'px'; | 1671 this.style.height = height + 'px'; |
1599 | 1672 |
1600 if (!this.wrapper_) { // First time, create DOM. | 1673 if (!this.wrapper_) { // First time, create DOM. |
1601 this.container_.appendChild(this); | 1674 this.container_.appendChild(this); |
1602 var border = util.createChild(this, 'img-border'); | 1675 var border = util.createChild(this, 'img-border'); |
1603 this.wrapper_ = util.createChild(border, 'img-wrapper'); | 1676 this.wrapper_ = util.createChild(border, 'img-wrapper'); |
1604 } | 1677 } |
1605 if (this.hasAttribute('selected')) | 1678 if (this.hasAttribute('selected')) |
1606 this.scrollIntoView(); | 1679 this.scrollIntoView(false); |
1607 | 1680 |
1608 this.thumbnailLoader_.attachImage(this.wrapper_, | 1681 this.thumbnailLoader_.attachImage(this.wrapper_, |
1609 ThumbnailLoader.FillMode.FILL); | 1682 ThumbnailLoader.FillMode.FILL); |
1610 }; | 1683 }; |
1611 | 1684 |
1612 /** | 1685 /** |
1613 * If the tile is not fully visible scroll the parent to make it fully visible. | 1686 * If the tile is not fully visible scroll the parent to make it fully visible. |
| 1687 * @param {boolean} opt_animated True, if scroll should be animated, |
| 1688 * default: true. |
1614 */ | 1689 */ |
1615 Mosaic.Tile.prototype.scrollIntoView = function() { | 1690 Mosaic.Tile.prototype.scrollIntoView = function(opt_animated) { |
1616 if (this.left_ == null) // Not laid out. | 1691 if (this.left_ == null) // Not laid out. |
1617 return; | 1692 return; |
1618 | 1693 |
1619 if (this.left_ < this.container_.scrollLeft) { | 1694 var targetPosition; |
1620 this.container_.scrollLeft = this.left_; | 1695 var tileLeft = this.left_ - Mosaic.Layout.SCROLL_MARGIN; |
| 1696 if (tileLeft < this.container_.scrollLeft) { |
| 1697 targetPosition = tileLeft; |
1621 } else { | 1698 } else { |
1622 var tileRight = this.left_ + this.width_; | 1699 var tileRight = this.left_ + this.width_ + Mosaic.Layout.SCROLL_MARGIN; |
1623 var scrollRight = this.container_.scrollLeft + this.container_.clientWidth; | 1700 var scrollRight = this.container_.scrollLeft + this.container_.clientWidth; |
1624 if (tileRight > scrollRight) | 1701 if (tileRight > scrollRight) |
1625 this.container_.scrollLeft = tileRight - this.container_.clientWidth; | 1702 targetPosition = tileRight - this.container_.clientWidth; |
| 1703 } |
| 1704 |
| 1705 if (targetPosition) { |
| 1706 if (opt_animated === false) |
| 1707 this.container_.scrollLeft = targetPosition; |
| 1708 else |
| 1709 this.container_.animatedScrollTo(targetPosition); |
1626 } | 1710 } |
1627 }; | 1711 }; |
1628 | 1712 |
1629 /** | 1713 /** |
1630 * @return {Rect} Rectangle occupied by the tile's image, | 1714 * @return {Rect} Rectangle occupied by the tile's image, |
1631 * relative to the viewport. | 1715 * relative to the viewport. |
1632 */ | 1716 */ |
1633 Mosaic.Tile.prototype.getImageRect = function() { | 1717 Mosaic.Tile.prototype.getImageRect = function() { |
1634 if (this.left_ == null) // Not laid out. | 1718 if (this.left_ == null) // Not laid out. |
1635 return null; | 1719 return null; |
1636 | 1720 |
1637 var margin = Mosaic.Layout.SPACING / 2; | 1721 var margin = Mosaic.Layout.SPACING / 2; |
1638 return new Rect(this.left_ - this.container_.scrollLeft, this.top_, | 1722 return new Rect(this.left_ - this.container_.scrollLeft, this.top_, |
1639 this.width_, this.height_).inflate(-margin, -margin); | 1723 this.width_, this.height_).inflate(-margin, -margin); |
1640 }; | 1724 }; |
1641 | 1725 |
1642 /** | 1726 /** |
1643 * @return {number} X coordinate of the tile center. | 1727 * @return {number} X coordinate of the tile center. |
1644 */ | 1728 */ |
1645 Mosaic.Tile.prototype.getCenterX = function() { | 1729 Mosaic.Tile.prototype.getCenterX = function() { |
1646 return this.left_ + Math.round(this.width_ / 2); | 1730 return this.left_ + Math.round(this.width_ / 2); |
1647 }; | 1731 }; |
OLD | NEW |