| Index: chrome/browser/resources/file_manager/js/photo/mosaic_mode.js
|
| diff --git a/chrome/browser/resources/file_manager/js/photo/mosaic_mode.js b/chrome/browser/resources/file_manager/js/photo/mosaic_mode.js
|
| deleted file mode 100644
|
| index 58cf9cd8dfebb4660a33cc0ff9aa151e7e038310..0000000000000000000000000000000000000000
|
| --- a/chrome/browser/resources/file_manager/js/photo/mosaic_mode.js
|
| +++ /dev/null
|
| @@ -1,2013 +0,0 @@
|
| -// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -
|
| -'use strict';
|
| -
|
| -/**
|
| - * @param {Element} container Content container.
|
| - * @param {cr.ui.ArrayDataModel} dataModel Data model.
|
| - * @param {cr.ui.ListSelectionModel} selectionModel Selection model.
|
| - * @param {MetadataCache} metadataCache Metadata cache.
|
| - * @param {function} toggleMode Function to switch to the Slide mode.
|
| - * @constructor
|
| - */
|
| -function MosaicMode(
|
| - container, dataModel, selectionModel, metadataCache, toggleMode) {
|
| - this.mosaic_ = new Mosaic(
|
| - container.ownerDocument, dataModel, selectionModel, metadataCache);
|
| - container.appendChild(this.mosaic_);
|
| -
|
| - this.toggleMode_ = toggleMode;
|
| - this.mosaic_.addEventListener('dblclick', this.toggleMode_);
|
| - this.showingTimeoutID_ = null;
|
| -}
|
| -
|
| -/**
|
| - * @return {Mosaic} The mosaic control.
|
| - */
|
| -MosaicMode.prototype.getMosaic = function() { return this.mosaic_ };
|
| -
|
| -/**
|
| - * @return {string} Mode name.
|
| - */
|
| -MosaicMode.prototype.getName = function() { return 'mosaic' };
|
| -
|
| -/**
|
| - * @return {string} Mode title.
|
| - */
|
| -MosaicMode.prototype.getTitle = function() { return 'GALLERY_MOSAIC' };
|
| -
|
| -/**
|
| - * Execute an action (this mode has no busy state).
|
| - * @param {function} action Action to execute.
|
| - */
|
| -MosaicMode.prototype.executeWhenReady = function(action) { action() };
|
| -
|
| -/**
|
| - * @return {boolean} Always true (no toolbar fading in this mode).
|
| - */
|
| -MosaicMode.prototype.hasActiveTool = function() { return true };
|
| -
|
| -/**
|
| - * Keydown handler.
|
| - *
|
| - * @param {Event} event Event.
|
| - * @return {boolean} True if processed.
|
| - */
|
| -MosaicMode.prototype.onKeyDown = function(event) {
|
| - switch (util.getKeyModifiers(event) + event.keyIdentifier) {
|
| - case 'Enter':
|
| - this.toggleMode_();
|
| - return true;
|
| - }
|
| - return this.mosaic_.onKeyDown(event);
|
| -};
|
| -
|
| -////////////////////////////////////////////////////////////////////////////////
|
| -
|
| -/**
|
| - * Mosaic control.
|
| - *
|
| - * @param {Document} document Document.
|
| - * @param {cr.ui.ArrayDataModel} dataModel Data model.
|
| - * @param {cr.ui.ListSelectionModel} selectionModel Selection model.
|
| - * @param {MetadataCache} metadataCache Metadata cache.
|
| - * @return {Element} Mosaic element.
|
| - * @constructor
|
| - */
|
| -function Mosaic(document, dataModel, selectionModel, metadataCache) {
|
| - var self = document.createElement('div');
|
| - Mosaic.decorate(self, dataModel, selectionModel, metadataCache);
|
| - return self;
|
| -}
|
| -
|
| -/**
|
| - * Inherit from HTMLDivElement.
|
| - */
|
| -Mosaic.prototype.__proto__ = HTMLDivElement.prototype;
|
| -
|
| -/**
|
| - * Default layout delay in ms.
|
| - * @const
|
| - * @type {number}
|
| - */
|
| -Mosaic.LAYOUT_DELAY = 200;
|
| -
|
| -/**
|
| - * Smooth scroll animation duration when scrolling using keyboard or
|
| - * clicking on a partly visible tile. In ms.
|
| - * @const
|
| - * @type {number}
|
| - */
|
| -Mosaic.ANIMATED_SCROLL_DURATION = 500;
|
| -
|
| -/**
|
| - * Decorate a Mosaic instance.
|
| - *
|
| - * @param {Mosaic} self Self pointer.
|
| - * @param {cr.ui.ArrayDataModel} dataModel Data model.
|
| - * @param {cr.ui.ListSelectionModel} selectionModel Selection model.
|
| - * @param {MetadataCache} metadataCache Metadata cache.
|
| - */
|
| -Mosaic.decorate = function(self, dataModel, selectionModel, metadataCache) {
|
| - self.__proto__ = Mosaic.prototype;
|
| - self.className = 'mosaic';
|
| -
|
| - self.dataModel_ = dataModel;
|
| - self.selectionModel_ = selectionModel;
|
| - self.metadataCache_ = metadataCache;
|
| -
|
| - // Initialization is completed lazily on the first call to |init|.
|
| -};
|
| -
|
| -/**
|
| - * Initialize the mosaic element.
|
| - */
|
| -Mosaic.prototype.init = function() {
|
| - if (this.tiles_)
|
| - return; // Already initialized, nothing to do.
|
| -
|
| - this.layoutModel_ = new Mosaic.Layout();
|
| - this.onResize_();
|
| -
|
| - this.selectionController_ =
|
| - new Mosaic.SelectionController(this.selectionModel_, this.layoutModel_);
|
| -
|
| - this.tiles_ = [];
|
| - for (var i = 0; i != this.dataModel_.length; i++)
|
| - this.tiles_.push(new Mosaic.Tile(this, this.dataModel_.item(i)));
|
| -
|
| - this.selectionModel_.selectedIndexes.forEach(function(index) {
|
| - this.tiles_[index].select(true);
|
| - }.bind(this));
|
| -
|
| - this.initTiles_(this.tiles_);
|
| -
|
| - // The listeners might be called while some tiles are still loading.
|
| - this.initListeners_();
|
| -};
|
| -
|
| -/**
|
| - * @return {boolean} Whether mosaic is initialized.
|
| - */
|
| -Mosaic.prototype.isInitialized = function() {
|
| - return !!this.tiles_;
|
| -};
|
| -
|
| -/**
|
| - * Start listening to events.
|
| - *
|
| - * We keep listening to events even when the mosaic is hidden in order to
|
| - * keep the layout up to date.
|
| - *
|
| - * @private
|
| - */
|
| -Mosaic.prototype.initListeners_ = function() {
|
| - this.ownerDocument.defaultView.addEventListener(
|
| - 'resize', this.onResize_.bind(this));
|
| -
|
| - var mouseEventBound = this.onMouseEvent_.bind(this);
|
| - this.addEventListener('mousemove', mouseEventBound);
|
| - this.addEventListener('mousedown', mouseEventBound);
|
| - this.addEventListener('mouseup', mouseEventBound);
|
| - this.addEventListener('scroll', this.onScroll_.bind(this));
|
| -
|
| - this.selectionModel_.addEventListener('change', this.onSelection_.bind(this));
|
| - this.selectionModel_.addEventListener('leadIndexChange',
|
| - this.onLeadChange_.bind(this));
|
| -
|
| - this.dataModel_.addEventListener('splice', this.onSplice_.bind(this));
|
| - this.dataModel_.addEventListener('content', this.onContentChange_.bind(this));
|
| -};
|
| -
|
| -/**
|
| - * Smoothly scrolls the container to the specified position using
|
| - * f(x) = sqrt(x) speed function normalized to animation duration.
|
| - * @param {number} targetPosition Horizontal scroll position in pixels.
|
| - */
|
| -Mosaic.prototype.animatedScrollTo = function(targetPosition) {
|
| - if (this.scrollAnimation_) {
|
| - webkitCancelAnimationFrame(this.scrollAnimation_);
|
| - this.scrollAnimation_ = null;
|
| - }
|
| -
|
| - // Mouse move events are fired without touching the mouse because of scrolling
|
| - // the container. Therefore, these events have to be suppressed.
|
| - this.suppressHovering_ = true;
|
| -
|
| - // Calculates integral area from t1 to t2 of f(x) = sqrt(x) dx.
|
| - var integral = function(t1, t2) {
|
| - return 2.0 / 3.0 * Math.pow(t2, 3.0 / 2.0) -
|
| - 2.0 / 3.0 * Math.pow(t1, 3.0 / 2.0);
|
| - };
|
| -
|
| - var delta = targetPosition - this.scrollLeft;
|
| - var factor = delta / integral(0, Mosaic.ANIMATED_SCROLL_DURATION);
|
| - var startTime = Date.now();
|
| - var lastPosition = 0;
|
| - var scrollOffset = this.scrollLeft;
|
| -
|
| - var animationFrame = function() {
|
| - var position = Date.now() - startTime;
|
| - var step = factor *
|
| - integral(Math.max(0, Mosaic.ANIMATED_SCROLL_DURATION - position),
|
| - Math.max(0, Mosaic.ANIMATED_SCROLL_DURATION - lastPosition));
|
| - scrollOffset += step;
|
| -
|
| - var oldScrollLeft = this.scrollLeft;
|
| - var newScrollLeft = Math.round(scrollOffset);
|
| -
|
| - if (oldScrollLeft != newScrollLeft)
|
| - this.scrollLeft = newScrollLeft;
|
| -
|
| - if (step == 0 || this.scrollLeft != newScrollLeft) {
|
| - this.scrollAnimation_ = null;
|
| - // Release the hovering lock after a safe delay to avoid hovering
|
| - // a tile because of altering |this.scrollLeft|.
|
| - setTimeout(function() {
|
| - if (!this.scrollAnimation_)
|
| - this.suppressHovering_ = false;
|
| - }.bind(this), 100);
|
| - } else {
|
| - // Continue the animation.
|
| - this.scrollAnimation_ = requestAnimationFrame(animationFrame);
|
| - }
|
| -
|
| - lastPosition = position;
|
| - }.bind(this);
|
| -
|
| - // Start the animation.
|
| - this.scrollAnimation_ = requestAnimationFrame(animationFrame);
|
| -};
|
| -
|
| -/**
|
| - * @return {Mosaic.Tile} Selected tile or undefined if no selection.
|
| - */
|
| -Mosaic.prototype.getSelectedTile = function() {
|
| - return this.tiles_ && this.tiles_[this.selectionModel_.selectedIndex];
|
| -};
|
| -
|
| -/**
|
| - * @param {number} index Tile index.
|
| - * @return {Rect} Tile's image rectangle.
|
| - */
|
| -Mosaic.prototype.getTileRect = function(index) {
|
| - var tile = this.tiles_[index];
|
| - return tile && tile.getImageRect();
|
| -};
|
| -
|
| -/**
|
| - * @param {number} index Tile index.
|
| - * Scroll the given tile into the viewport.
|
| - */
|
| -Mosaic.prototype.scrollIntoView = function(index) {
|
| - var tile = this.tiles_[index];
|
| - if (tile) tile.scrollIntoView();
|
| -};
|
| -
|
| -/**
|
| - * Initializes multiple tiles.
|
| - *
|
| - * @param {Array.<Mosaic.Tile>} tiles Array of tiles.
|
| - * @param {function()=} opt_callback Completion callback.
|
| - * @private
|
| - */
|
| -Mosaic.prototype.initTiles_ = function(tiles, opt_callback) {
|
| - // We do not want to use tile indices in asynchronous operations because they
|
| - // do not survive data model splices. Copy tile references instead.
|
| - tiles = tiles.slice();
|
| -
|
| - // Throttle the metadata access so that we do not overwhelm the file system.
|
| - var MAX_CHUNK_SIZE = 10;
|
| -
|
| - var loadChunk = function() {
|
| - if (!tiles.length) {
|
| - if (opt_callback) opt_callback();
|
| - return;
|
| - }
|
| - var chunkSize = Math.min(tiles.length, MAX_CHUNK_SIZE);
|
| - var loaded = 0;
|
| - for (var i = 0; i != chunkSize; i++) {
|
| - this.initTile_(tiles.shift(), function() {
|
| - if (++loaded == chunkSize) {
|
| - this.layout();
|
| - loadChunk();
|
| - }
|
| - }.bind(this));
|
| - }
|
| - }.bind(this);
|
| -
|
| - loadChunk();
|
| -};
|
| -
|
| -/**
|
| - * Initializes a single tile.
|
| - *
|
| - * @param {Mosaic.Tile} tile Tile.
|
| - * @param {function()} callback Completion callback.
|
| - * @private
|
| - */
|
| -Mosaic.prototype.initTile_ = function(tile, callback) {
|
| - var url = tile.getItem().getUrl();
|
| - var onImageMeasured = callback;
|
| - this.metadataCache_.get(url, Gallery.METADATA_TYPE,
|
| - function(metadata) {
|
| - tile.init(metadata, onImageMeasured);
|
| - });
|
| -};
|
| -
|
| -/**
|
| - * Reload all tiles.
|
| - */
|
| -Mosaic.prototype.reload = function() {
|
| - this.layoutModel_.reset_();
|
| - this.tiles_.forEach(function(t) { t.markUnloaded() });
|
| - this.initTiles_(this.tiles_);
|
| -};
|
| -
|
| -/**
|
| - * Layout the tiles in the order of their indices.
|
| - *
|
| - * Starts where it last stopped (at #0 the first time).
|
| - * Stops when all tiles are processed or when the next tile is still loading.
|
| - */
|
| -Mosaic.prototype.layout = function() {
|
| - if (this.layoutTimer_) {
|
| - clearTimeout(this.layoutTimer_);
|
| - this.layoutTimer_ = null;
|
| - }
|
| - while (true) {
|
| - var index = this.layoutModel_.getTileCount();
|
| - if (index == this.tiles_.length)
|
| - break; // All tiles done.
|
| - var tile = this.tiles_[index];
|
| - if (!tile.isInitialized())
|
| - break; // Next layout will try to restart from here.
|
| - this.layoutModel_.add(tile, index + 1 == this.tiles_.length);
|
| - }
|
| - this.loadVisibleTiles_();
|
| -};
|
| -
|
| -/**
|
| - * Schedule the layout.
|
| - *
|
| - * @param {number=} opt_delay Delay in ms.
|
| - */
|
| -Mosaic.prototype.scheduleLayout = function(opt_delay) {
|
| - if (!this.layoutTimer_) {
|
| - this.layoutTimer_ = setTimeout(function() {
|
| - this.layoutTimer_ = null;
|
| - this.layout();
|
| - }.bind(this), opt_delay || 0);
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Resize handler.
|
| - *
|
| - * @private
|
| - */
|
| -Mosaic.prototype.onResize_ = function() {
|
| - this.layoutModel_.setViewportSize(this.clientWidth, this.clientHeight -
|
| - (Mosaic.Layout.PADDING_TOP + Mosaic.Layout.PADDING_BOTTOM));
|
| - this.scheduleLayout();
|
| -};
|
| -
|
| -/**
|
| - * Mouse event handler.
|
| - *
|
| - * @param {Event} event Event.
|
| - * @private
|
| - */
|
| -Mosaic.prototype.onMouseEvent_ = function(event) {
|
| - // Navigating with mouse, enable hover state.
|
| - if (!this.suppressHovering_)
|
| - this.classList.add('hover-visible');
|
| -
|
| - if (event.type == 'mousemove')
|
| - return;
|
| -
|
| - var index = -1;
|
| - for (var target = event.target;
|
| - target && (target != this);
|
| - target = target.parentNode) {
|
| - if (target.classList.contains('mosaic-tile')) {
|
| - index = this.dataModel_.indexOf(target.getItem());
|
| - break;
|
| - }
|
| - }
|
| - this.selectionController_.handlePointerDownUp(event, index);
|
| -};
|
| -
|
| -/**
|
| - * Scroll handler.
|
| - * @private
|
| - */
|
| -Mosaic.prototype.onScroll_ = function() {
|
| - requestAnimationFrame(function() {
|
| - this.loadVisibleTiles_();
|
| - }.bind(this));
|
| -};
|
| -
|
| -/**
|
| - * Selection change handler.
|
| - *
|
| - * @param {Event} event Event.
|
| - * @private
|
| - */
|
| -Mosaic.prototype.onSelection_ = function(event) {
|
| - for (var i = 0; i != event.changes.length; i++) {
|
| - var change = event.changes[i];
|
| - var tile = this.tiles_[change.index];
|
| - if (tile) tile.select(change.selected);
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Lead item change handler.
|
| - *
|
| - * @param {Event} event Event.
|
| - * @private
|
| - */
|
| -Mosaic.prototype.onLeadChange_ = function(event) {
|
| - var index = event.newValue;
|
| - if (index >= 0) {
|
| - var tile = this.tiles_[index];
|
| - if (tile) tile.scrollIntoView();
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Splice event handler.
|
| - *
|
| - * @param {Event} event Event.
|
| - * @private
|
| - */
|
| -Mosaic.prototype.onSplice_ = function(event) {
|
| - var index = event.index;
|
| - this.layoutModel_.invalidateFromTile_(index);
|
| -
|
| - if (event.removed.length) {
|
| - for (var t = 0; t != event.removed.length; t++)
|
| - this.removeChild(this.tiles_[index + t]);
|
| -
|
| - this.tiles_.splice(index, event.removed.length);
|
| - this.scheduleLayout(Mosaic.LAYOUT_DELAY);
|
| - }
|
| -
|
| - if (event.added.length) {
|
| - var newTiles = [];
|
| - for (var t = 0; t != event.added.length; t++)
|
| - newTiles.push(new Mosaic.Tile(this, this.dataModel_.item(index + t)));
|
| -
|
| - this.tiles_.splice.apply(this.tiles_, [index, 0].concat(newTiles));
|
| - this.initTiles_(newTiles);
|
| - }
|
| -
|
| - if (this.tiles_.length != this.dataModel_.length)
|
| - console.error('Mosaic is out of sync');
|
| -};
|
| -
|
| -/**
|
| - * Content change handler.
|
| - *
|
| - * @param {Event} event Event.
|
| - * @private
|
| - */
|
| -Mosaic.prototype.onContentChange_ = function(event) {
|
| - if (!this.tiles_)
|
| - return;
|
| -
|
| - if (!event.metadata)
|
| - return; // Thumbnail unchanged, nothing to do.
|
| -
|
| - var index = this.dataModel_.indexOf(event.item);
|
| - if (index != this.selectionModel_.selectedIndex)
|
| - console.error('Content changed for unselected item');
|
| -
|
| - this.layoutModel_.invalidateFromTile_(index);
|
| - this.tiles_[index].init(event.metadata, function() {
|
| - this.tiles_[index].unload();
|
| - this.tiles_[index].load(
|
| - Mosaic.Tile.LoadMode.HIGH_DPI,
|
| - this.scheduleLayout.bind(this, Mosaic.LAYOUT_DELAY));
|
| - }.bind(this));
|
| -};
|
| -
|
| -/**
|
| - * Keydown event handler.
|
| - *
|
| - * @param {Event} event Event.
|
| - * @return {boolean} True if the event has been consumed.
|
| - */
|
| -Mosaic.prototype.onKeyDown = function(event) {
|
| - this.selectionController_.handleKeyDown(event);
|
| - if (event.defaultPrevented) // Navigating with keyboard, hide hover state.
|
| - this.classList.remove('hover-visible');
|
| - return event.defaultPrevented;
|
| -};
|
| -
|
| -/**
|
| - * @return {boolean} True if the mosaic zoom effect can be applied. It is
|
| - * too slow if there are to many images.
|
| - * TODO(kaznacheev): Consider unloading the images that are out of the viewport.
|
| - */
|
| -Mosaic.prototype.canZoom = function() {
|
| - return this.tiles_.length < 100;
|
| -};
|
| -
|
| -/**
|
| - * Show the mosaic.
|
| - */
|
| -Mosaic.prototype.show = function() {
|
| - var duration = ImageView.MODE_TRANSITION_DURATION;
|
| - if (this.canZoom()) {
|
| - // Fade in in parallel with the zoom effect.
|
| - this.setAttribute('visible', 'zooming');
|
| - } else {
|
| - // Mosaic is not animating but the large image is. Fade in the mosaic
|
| - // shortly before the large image animation is done.
|
| - duration -= 100;
|
| - }
|
| - this.showingTimeoutID_ = setTimeout(function() {
|
| - this.showingTimeoutID_ = null;
|
| - // Make the selection visible.
|
| - // If the mosaic is not animated it will start fading in now.
|
| - this.setAttribute('visible', 'normal');
|
| - this.loadVisibleTiles_();
|
| - }.bind(this), duration);
|
| -};
|
| -
|
| -/**
|
| - * Hide the mosaic.
|
| - */
|
| -Mosaic.prototype.hide = function() {
|
| - if (this.showingTimeoutID_ != null) {
|
| - clearTimeout(this.showingTimeoutID_);
|
| - this.showingTimeoutID_ = null;
|
| - }
|
| - this.removeAttribute('visible');
|
| -};
|
| -
|
| -/**
|
| - * Checks if the mosaic view is visible.
|
| - * @return {boolean} True if visible, false otherwise.
|
| - * @private
|
| - */
|
| -Mosaic.prototype.isVisible_ = function() {
|
| - return this.hasAttribute('visible');
|
| -};
|
| -
|
| -/**
|
| - * Loads visible tiles. Ignores consecutive calls. Does not reload already
|
| - * loaded images.
|
| - * @private
|
| - */
|
| -Mosaic.prototype.loadVisibleTiles_ = function() {
|
| - if (this.loadVisibleTilesSuppressed_) {
|
| - this.loadVisibleTilesScheduled_ = true;
|
| - return;
|
| - }
|
| -
|
| - this.loadVisibleTilesSuppressed_ = true;
|
| - this.loadVisibleTilesScheduled_ = false;
|
| - setTimeout(function() {
|
| - this.loadVisibleTilesSuppressed_ = false;
|
| - if (this.loadVisibleTilesScheduled_)
|
| - this.loadVisibleTiles_();
|
| - }.bind(this), 100);
|
| -
|
| - // Tiles only in the viewport (visible).
|
| - var visibleRect = new Rect(0,
|
| - 0,
|
| - this.clientWidth,
|
| - this.clientHeight);
|
| -
|
| - // Tiles in the viewport and also some distance on the left and right.
|
| - var renderableRect = new Rect(-this.clientWidth,
|
| - 0,
|
| - 3 * this.clientWidth,
|
| - this.clientHeight);
|
| -
|
| - // Unload tiles out of scope.
|
| - for (var index = 0; index < this.tiles_.length; index++) {
|
| - var tile = this.tiles_[index];
|
| - var imageRect = tile.getImageRect();
|
| - // Unload a thumbnail.
|
| - if (imageRect && !imageRect.intersects(renderableRect))
|
| - tile.unload();
|
| - }
|
| -
|
| - // Load the visible tiles first.
|
| - var allVisibleLoaded = true;
|
| - // Show high-dpi only when the mosaic view is visible.
|
| - var loadMode = this.isVisible_() ? Mosaic.Tile.LoadMode.HIGH_DPI :
|
| - Mosaic.Tile.LoadMode.LOW_DPI;
|
| - for (var index = 0; index < this.tiles_.length; index++) {
|
| - var tile = this.tiles_[index];
|
| - var imageRect = tile.getImageRect();
|
| - // Load a thumbnail.
|
| - if (!tile.isLoading(loadMode) && !tile.isLoaded(loadMode) && imageRect &&
|
| - imageRect.intersects(visibleRect)) {
|
| - tile.load(loadMode, function() {});
|
| - allVisibleLoaded = false;
|
| - }
|
| - }
|
| -
|
| - // Load also another, nearby, if the visible has been already loaded.
|
| - if (allVisibleLoaded) {
|
| - for (var index = 0; index < this.tiles_.length; index++) {
|
| - var tile = this.tiles_[index];
|
| - var imageRect = tile.getImageRect();
|
| - // Load a thumbnail.
|
| - if (!tile.isLoading() && !tile.isLoaded() && imageRect &&
|
| - imageRect.intersects(renderableRect)) {
|
| - tile.load(Mosaic.Tile.LoadMode.LOW_DPI, function() {});
|
| - }
|
| - }
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Apply or reset the zoom transform.
|
| - *
|
| - * @param {Rect} tileRect Tile rectangle. Reset the transform if null.
|
| - * @param {Rect} imageRect Large image rectangle. Reset the transform if null.
|
| - * @param {boolean=} opt_instant True of the transition should be instant.
|
| - */
|
| -Mosaic.prototype.transform = function(tileRect, imageRect, opt_instant) {
|
| - if (opt_instant) {
|
| - this.style.webkitTransitionDuration = '0';
|
| - } else {
|
| - this.style.webkitTransitionDuration =
|
| - ImageView.MODE_TRANSITION_DURATION + 'ms';
|
| - }
|
| -
|
| - if (this.canZoom() && tileRect && imageRect) {
|
| - var scaleX = imageRect.width / tileRect.width;
|
| - var scaleY = imageRect.height / tileRect.height;
|
| - var shiftX = (imageRect.left + imageRect.width / 2) -
|
| - (tileRect.left + tileRect.width / 2);
|
| - var shiftY = (imageRect.top + imageRect.height / 2) -
|
| - (tileRect.top + tileRect.height / 2);
|
| - this.style.webkitTransform =
|
| - 'translate(' + shiftX * scaleX + 'px, ' + shiftY * scaleY + 'px)' +
|
| - 'scaleX(' + scaleX + ') scaleY(' + scaleY + ')';
|
| - } else {
|
| - this.style.webkitTransform = '';
|
| - }
|
| -};
|
| -
|
| -////////////////////////////////////////////////////////////////////////////////
|
| -
|
| -/**
|
| - * Creates a selection controller that is to be used with grid.
|
| - * @param {cr.ui.ListSelectionModel} selectionModel The selection model to
|
| - * interact with.
|
| - * @param {Mosaic.Layout} layoutModel The layout model to use.
|
| - * @constructor
|
| - * @extends {!cr.ui.ListSelectionController}
|
| - */
|
| -Mosaic.SelectionController = function(selectionModel, layoutModel) {
|
| - cr.ui.ListSelectionController.call(this, selectionModel);
|
| - this.layoutModel_ = layoutModel;
|
| -};
|
| -
|
| -/**
|
| - * Extends cr.ui.ListSelectionController.
|
| - */
|
| -Mosaic.SelectionController.prototype.__proto__ =
|
| - cr.ui.ListSelectionController.prototype;
|
| -
|
| -/** @override */
|
| -Mosaic.SelectionController.prototype.getLastIndex = function() {
|
| - return this.layoutModel_.getLaidOutTileCount() - 1;
|
| -};
|
| -
|
| -/** @override */
|
| -Mosaic.SelectionController.prototype.getIndexBefore = function(index) {
|
| - return this.layoutModel_.getHorizontalAdjacentIndex(index, -1);
|
| -};
|
| -
|
| -/** @override */
|
| -Mosaic.SelectionController.prototype.getIndexAfter = function(index) {
|
| - return this.layoutModel_.getHorizontalAdjacentIndex(index, 1);
|
| -};
|
| -
|
| -/** @override */
|
| -Mosaic.SelectionController.prototype.getIndexAbove = function(index) {
|
| - return this.layoutModel_.getVerticalAdjacentIndex(index, -1);
|
| -};
|
| -
|
| -/** @override */
|
| -Mosaic.SelectionController.prototype.getIndexBelow = function(index) {
|
| - return this.layoutModel_.getVerticalAdjacentIndex(index, 1);
|
| -};
|
| -
|
| -////////////////////////////////////////////////////////////////////////////////
|
| -
|
| -/**
|
| - * Mosaic layout.
|
| - *
|
| - * @param {string=} opt_mode Layout mode.
|
| - * @param {Mosaic.Density=} opt_maxDensity Layout density.
|
| - * @constructor
|
| - */
|
| -Mosaic.Layout = function(opt_mode, opt_maxDensity) {
|
| - this.mode_ = opt_mode || Mosaic.Layout.MODE_TENTATIVE;
|
| - this.maxDensity_ = opt_maxDensity || Mosaic.Density.createHighest();
|
| - this.reset_();
|
| -};
|
| -
|
| -/**
|
| - * Blank space at the top of the mosaic element. We do not do that in CSS
|
| - * to make transition effects easier.
|
| - */
|
| -Mosaic.Layout.PADDING_TOP = 50;
|
| -
|
| -/**
|
| - * Blank space at the bottom of the mosaic element.
|
| - */
|
| -Mosaic.Layout.PADDING_BOTTOM = 50;
|
| -
|
| -/**
|
| - * Horizontal and vertical spacing between images. Should be kept in sync
|
| - * with the style of .mosaic-item in gallery.css (= 2 * ( 4 + 1))
|
| - */
|
| -Mosaic.Layout.SPACING = 10;
|
| -
|
| -/**
|
| - * Margin for scrolling using keyboard. Distance between a selected tile
|
| - * and window border.
|
| - */
|
| -Mosaic.Layout.SCROLL_MARGIN = 30;
|
| -
|
| -/**
|
| - * Layout mode: commit to DOM immediately.
|
| - */
|
| -Mosaic.Layout.MODE_FINAL = 'final';
|
| -
|
| -/**
|
| - * Layout mode: do not commit layout to DOM until it is complete or the viewport
|
| - * overflows.
|
| - */
|
| -Mosaic.Layout.MODE_TENTATIVE = 'tentative';
|
| -
|
| -/**
|
| - * Layout mode: never commit layout to DOM.
|
| - */
|
| -Mosaic.Layout.MODE_DRY_RUN = 'dry_run';
|
| -
|
| -/**
|
| - * Reset the layout.
|
| - *
|
| - * @private
|
| - */
|
| -Mosaic.Layout.prototype.reset_ = function() {
|
| - this.columns_ = [];
|
| - this.newColumn_ = null;
|
| - this.density_ = Mosaic.Density.createLowest();
|
| - if (this.mode_ != Mosaic.Layout.MODE_DRY_RUN) // DRY_RUN is sticky.
|
| - this.mode_ = Mosaic.Layout.MODE_TENTATIVE;
|
| -};
|
| -
|
| -/**
|
| - * @param {number} width Viewport width.
|
| - * @param {number} height Viewport height.
|
| - */
|
| -Mosaic.Layout.prototype.setViewportSize = function(width, height) {
|
| - this.viewportWidth_ = width;
|
| - this.viewportHeight_ = height;
|
| - this.reset_();
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Total width of the layout.
|
| - */
|
| -Mosaic.Layout.prototype.getWidth = function() {
|
| - var lastColumn = this.getLastColumn_();
|
| - return lastColumn ? lastColumn.getRight() : 0;
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Total height of the layout.
|
| - */
|
| -Mosaic.Layout.prototype.getHeight = function() {
|
| - var firstColumn = this.columns_[0];
|
| - return firstColumn ? firstColumn.getHeight() : 0;
|
| -};
|
| -
|
| -/**
|
| - * @return {Array.<Mosaic.Tile>} All tiles in the layout.
|
| - */
|
| -Mosaic.Layout.prototype.getTiles = function() {
|
| - return Array.prototype.concat.apply([],
|
| - this.columns_.map(function(c) { return c.getTiles() }));
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Total number of tiles added to the layout.
|
| - */
|
| -Mosaic.Layout.prototype.getTileCount = function() {
|
| - return this.getLaidOutTileCount() +
|
| - (this.newColumn_ ? this.newColumn_.getTileCount() : 0);
|
| -};
|
| -
|
| -/**
|
| - * @return {Mosaic.Column} The last column or null for empty layout.
|
| - * @private
|
| - */
|
| -Mosaic.Layout.prototype.getLastColumn_ = function() {
|
| - return this.columns_.length ? this.columns_[this.columns_.length - 1] : null;
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Total number of tiles in completed columns.
|
| - */
|
| -Mosaic.Layout.prototype.getLaidOutTileCount = function() {
|
| - var lastColumn = this.getLastColumn_();
|
| - return lastColumn ? lastColumn.getNextTileIndex() : 0;
|
| -};
|
| -
|
| -/**
|
| - * Add a tile to the layout.
|
| - *
|
| - * @param {Mosaic.Tile} tile The tile to be added.
|
| - * @param {boolean} isLast True if this tile is the last.
|
| - */
|
| -Mosaic.Layout.prototype.add = function(tile, isLast) {
|
| - var layoutQueue = [tile];
|
| -
|
| - // There are two levels of backtracking in the layout algorithm.
|
| - // |Mosaic.Layout.density_| tracks the state of the 'global' backtracking
|
| - // which aims to use as much of the viewport space as possible.
|
| - // It starts with the lowest density and increases it until the layout
|
| - // fits into the viewport. If it does not fit even at the highest density,
|
| - // the layout continues with the highest density.
|
| - //
|
| - // |Mosaic.Column.density_| tracks the state of the 'local' backtracking
|
| - // which aims to avoid producing unnaturally looking columns.
|
| - // It starts with the current global density and decreases it until the column
|
| - // looks nice.
|
| -
|
| - while (layoutQueue.length) {
|
| - if (!this.newColumn_) {
|
| - var lastColumn = this.getLastColumn_();
|
| - this.newColumn_ = new Mosaic.Column(
|
| - this.columns_.length,
|
| - lastColumn ? lastColumn.getNextRowIndex() : 0,
|
| - lastColumn ? lastColumn.getNextTileIndex() : 0,
|
| - lastColumn ? lastColumn.getRight() : 0,
|
| - this.viewportHeight_,
|
| - this.density_.clone());
|
| - }
|
| -
|
| - this.newColumn_.add(layoutQueue.shift());
|
| -
|
| - var isFinalColumn = isLast && !layoutQueue.length;
|
| -
|
| - if (!this.newColumn_.prepareLayout(isFinalColumn))
|
| - continue; // Column is incomplete.
|
| -
|
| - if (this.newColumn_.isSuboptimal()) {
|
| - layoutQueue = this.newColumn_.getTiles().concat(layoutQueue);
|
| - this.newColumn_.retryWithLowerDensity();
|
| - continue;
|
| - }
|
| -
|
| - this.columns_.push(this.newColumn_);
|
| - this.newColumn_ = null;
|
| -
|
| - if (this.mode_ == Mosaic.Layout.MODE_FINAL) {
|
| - this.getLastColumn_().layout();
|
| - continue;
|
| - }
|
| -
|
| - if (this.getWidth() > this.viewportWidth_) {
|
| - // Viewport completely filled.
|
| - if (this.density_.equals(this.maxDensity_)) {
|
| - // Max density reached, commit if tentative, just continue if dry run.
|
| - if (this.mode_ == Mosaic.Layout.MODE_TENTATIVE)
|
| - this.commit_();
|
| - continue;
|
| - }
|
| -
|
| - // Rollback the entire layout, retry with higher density.
|
| - layoutQueue = this.getTiles().concat(layoutQueue);
|
| - this.columns_ = [];
|
| - this.density_.increase();
|
| - continue;
|
| - }
|
| -
|
| - if (isFinalColumn && this.mode_ == Mosaic.Layout.MODE_TENTATIVE) {
|
| - // The complete tentative layout fits into the viewport.
|
| - var stretched = this.findHorizontalLayout_();
|
| - if (stretched)
|
| - this.columns_ = stretched.columns_;
|
| - // Center the layout in the viewport and commit.
|
| - this.commit_((this.viewportWidth_ - this.getWidth()) / 2,
|
| - (this.viewportHeight_ - this.getHeight()) / 2);
|
| - }
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Commit the tentative layout.
|
| - *
|
| - * @param {number=} opt_offsetX Horizontal offset.
|
| - * @param {number=} opt_offsetY Vertical offset.
|
| - * @private
|
| - */
|
| -Mosaic.Layout.prototype.commit_ = function(opt_offsetX, opt_offsetY) {
|
| - console.assert(this.mode_ != Mosaic.Layout.MODE_FINAL,
|
| - 'Did not expect final layout');
|
| - for (var i = 0; i != this.columns_.length; i++) {
|
| - this.columns_[i].layout(opt_offsetX, opt_offsetY);
|
| - }
|
| - this.mode_ = Mosaic.Layout.MODE_FINAL;
|
| -};
|
| -
|
| -/**
|
| - * Find the most horizontally stretched layout built from the same tiles.
|
| - *
|
| - * The main layout algorithm fills the entire available viewport height.
|
| - * If there is too few tiles this results in a layout that is unnaturally
|
| - * stretched in the vertical direction.
|
| - *
|
| - * This method tries a number of smaller heights and returns the most
|
| - * horizontally stretched layout that still fits into the viewport.
|
| - *
|
| - * @return {Mosaic.Layout} A horizontally stretched layout.
|
| - * @private
|
| - */
|
| -Mosaic.Layout.prototype.findHorizontalLayout_ = function() {
|
| - // If the layout aspect ratio is not dramatically different from
|
| - // the viewport aspect ratio then there is no need to optimize.
|
| - if (this.getWidth() / this.getHeight() >
|
| - this.viewportWidth_ / this.viewportHeight_ * 0.9)
|
| - return null;
|
| -
|
| - var tiles = this.getTiles();
|
| - if (tiles.length == 1)
|
| - return null; // Single tile layout is always the same.
|
| -
|
| - var tileHeights = tiles.map(function(t) { return t.getMaxContentHeight() });
|
| - var minTileHeight = Math.min.apply(null, tileHeights);
|
| -
|
| - for (var h = minTileHeight; h < this.viewportHeight_; h += minTileHeight) {
|
| - var layout = new Mosaic.Layout(
|
| - Mosaic.Layout.MODE_DRY_RUN, this.density_.clone());
|
| - layout.setViewportSize(this.viewportWidth_, h);
|
| - for (var t = 0; t != tiles.length; t++)
|
| - layout.add(tiles[t], t + 1 == tiles.length);
|
| -
|
| - if (layout.getWidth() <= this.viewportWidth_)
|
| - return layout;
|
| - }
|
| -
|
| - return null;
|
| -};
|
| -
|
| -/**
|
| - * Invalidate the layout after the given tile was modified (added, deleted or
|
| - * changed dimensions).
|
| - *
|
| - * @param {number} index Tile index.
|
| - * @private
|
| - */
|
| -Mosaic.Layout.prototype.invalidateFromTile_ = function(index) {
|
| - var columnIndex = this.getColumnIndexByTile_(index);
|
| - if (columnIndex < 0)
|
| - return; // Index not in the layout, probably already invalidated.
|
| -
|
| - if (this.columns_[columnIndex].getLeft() >= this.viewportWidth_) {
|
| - // The columns to the right cover the entire viewport width, so there is no
|
| - // chance that the modified layout would fit into the viewport.
|
| - // No point in restarting the entire layout, keep the columns to the right.
|
| - console.assert(this.mode_ == Mosaic.Layout.MODE_FINAL,
|
| - 'Expected FINAL layout mode');
|
| - this.columns_ = this.columns_.slice(0, columnIndex);
|
| - this.newColumn_ = null;
|
| - } else {
|
| - // There is a chance that the modified layout would fit into the viewport.
|
| - this.reset_();
|
| - this.mode_ = Mosaic.Layout.MODE_TENTATIVE;
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Get the index of the tile to the left or to the right from the given tile.
|
| - *
|
| - * @param {number} index Tile index.
|
| - * @param {number} direction -1 for left, 1 for right.
|
| - * @return {number} Adjacent tile index.
|
| - */
|
| -Mosaic.Layout.prototype.getHorizontalAdjacentIndex = function(
|
| - index, direction) {
|
| - var column = this.getColumnIndexByTile_(index);
|
| - if (column < 0) {
|
| - console.error('Cannot find column for tile #' + index);
|
| - return -1;
|
| - }
|
| -
|
| - var row = this.columns_[column].getRowByTileIndex(index);
|
| - if (!row) {
|
| - console.error('Cannot find row for tile #' + index);
|
| - return -1;
|
| - }
|
| -
|
| - var sameRowNeighbourIndex = index + direction;
|
| - if (row.hasTile(sameRowNeighbourIndex))
|
| - return sameRowNeighbourIndex;
|
| -
|
| - var adjacentColumn = column + direction;
|
| - if (adjacentColumn < 0 || adjacentColumn == this.columns_.length)
|
| - return -1;
|
| -
|
| - return this.columns_[adjacentColumn].
|
| - getEdgeTileIndex_(row.getCenterY(), -direction);
|
| -};
|
| -
|
| -/**
|
| - * Get the index of the tile to the top or to the bottom from the given tile.
|
| - *
|
| - * @param {number} index Tile index.
|
| - * @param {number} direction -1 for above, 1 for below.
|
| - * @return {number} Adjacent tile index.
|
| - */
|
| -Mosaic.Layout.prototype.getVerticalAdjacentIndex = function(
|
| - index, direction) {
|
| - var column = this.getColumnIndexByTile_(index);
|
| - if (column < 0) {
|
| - console.error('Cannot find column for tile #' + index);
|
| - return -1;
|
| - }
|
| -
|
| - var row = this.columns_[column].getRowByTileIndex(index);
|
| - if (!row) {
|
| - console.error('Cannot find row for tile #' + index);
|
| - return -1;
|
| - }
|
| -
|
| - // Find the first item in the next row, or the last item in the previous row.
|
| - var adjacentRowNeighbourIndex =
|
| - row.getEdgeTileIndex_(direction) + direction;
|
| -
|
| - if (adjacentRowNeighbourIndex < 0 ||
|
| - adjacentRowNeighbourIndex > this.getTileCount() - 1)
|
| - return -1;
|
| -
|
| - if (!this.columns_[column].hasTile(adjacentRowNeighbourIndex)) {
|
| - // It is not in the current column, so return it.
|
| - return adjacentRowNeighbourIndex;
|
| - } else {
|
| - // It is in the current column, so we have to find optically the closest
|
| - // tile in the adjacent row.
|
| - var adjacentRow = this.columns_[column].getRowByTileIndex(
|
| - adjacentRowNeighbourIndex);
|
| - var previousTileCenterX = row.getTileByIndex(index).getCenterX();
|
| -
|
| - // Find the closest one.
|
| - var closestIndex = -1;
|
| - var closestDistance;
|
| - var adjacentRowTiles = adjacentRow.getTiles();
|
| - for (var t = 0; t != adjacentRowTiles.length; t++) {
|
| - var distance =
|
| - Math.abs(adjacentRowTiles[t].getCenterX() - previousTileCenterX);
|
| - if (closestIndex == -1 || distance < closestDistance) {
|
| - closestIndex = adjacentRow.getEdgeTileIndex_(-1) + t;
|
| - closestDistance = distance;
|
| - }
|
| - }
|
| - return closestIndex;
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * @param {number} index Tile index.
|
| - * @return {number} Index of the column containing the given tile.
|
| - * @private
|
| - */
|
| -Mosaic.Layout.prototype.getColumnIndexByTile_ = function(index) {
|
| - for (var c = 0; c != this.columns_.length; c++) {
|
| - if (this.columns_[c].hasTile(index))
|
| - return c;
|
| - }
|
| - return -1;
|
| -};
|
| -
|
| -/**
|
| - * Scale the given array of size values to satisfy 3 conditions:
|
| - * 1. The new sizes must be integer.
|
| - * 2. The new sizes must sum up to the given |total| value.
|
| - * 3. The relative proportions of the sizes should be as close to the original
|
| - * as possible.
|
| - *
|
| - * @param {Array.<number>} sizes Array of sizes.
|
| - * @param {number} newTotal New total size.
|
| - */
|
| -Mosaic.Layout.rescaleSizesToNewTotal = function(sizes, newTotal) {
|
| - var total = 0;
|
| -
|
| - var partialTotals = [0];
|
| - for (var i = 0; i != sizes.length; i++) {
|
| - total += sizes[i];
|
| - partialTotals.push(total);
|
| - }
|
| -
|
| - var scale = newTotal / total;
|
| -
|
| - for (i = 0; i != sizes.length; i++) {
|
| - sizes[i] = Math.round(partialTotals[i + 1] * scale) -
|
| - Math.round(partialTotals[i] * scale);
|
| - }
|
| -};
|
| -
|
| -////////////////////////////////////////////////////////////////////////////////
|
| -
|
| -/**
|
| - * Representation of the layout density.
|
| - *
|
| - * @param {number} horizontal Horizontal density, number tiles per row.
|
| - * @param {number} vertical Vertical density, frequency of rows forced to
|
| - * contain a single tile.
|
| - * @constructor
|
| - */
|
| -Mosaic.Density = function(horizontal, vertical) {
|
| - this.horizontal = horizontal;
|
| - this.vertical = vertical;
|
| -};
|
| -
|
| -/**
|
| - * Minimal horizontal density (tiles per row).
|
| - */
|
| -Mosaic.Density.MIN_HORIZONTAL = 1;
|
| -
|
| -/**
|
| - * Minimal horizontal density (tiles per row).
|
| - */
|
| -Mosaic.Density.MAX_HORIZONTAL = 3;
|
| -
|
| -/**
|
| - * Minimal vertical density: force 1 out of 2 rows to containt a single tile.
|
| - */
|
| -Mosaic.Density.MIN_VERTICAL = 2;
|
| -
|
| -/**
|
| - * Maximal vertical density: force 1 out of 3 rows to containt a single tile.
|
| - */
|
| -Mosaic.Density.MAX_VERTICAL = 3;
|
| -
|
| -/**
|
| - * @return {Mosaic.Density} Lowest density.
|
| - */
|
| -Mosaic.Density.createLowest = function() {
|
| - return new Mosaic.Density(
|
| - Mosaic.Density.MIN_HORIZONTAL,
|
| - Mosaic.Density.MIN_VERTICAL /* ignored when horizontal is at min */);
|
| -};
|
| -
|
| -/**
|
| - * @return {Mosaic.Density} Highest density.
|
| - */
|
| -Mosaic.Density.createHighest = function() {
|
| - return new Mosaic.Density(
|
| - Mosaic.Density.MAX_HORIZONTAL,
|
| - Mosaic.Density.MAX_VERTICAL);
|
| -};
|
| -
|
| -/**
|
| - * @return {Mosaic.Density} A clone of this density object.
|
| - */
|
| -Mosaic.Density.prototype.clone = function() {
|
| - return new Mosaic.Density(this.horizontal, this.vertical);
|
| -};
|
| -
|
| -/**
|
| - * @param {Mosaic.Density} that The other object.
|
| - * @return {boolean} True if equal.
|
| - */
|
| -Mosaic.Density.prototype.equals = function(that) {
|
| - return this.horizontal == that.horizontal &&
|
| - this.vertical == that.vertical;
|
| -};
|
| -
|
| -/**
|
| - * Increase the density to the next level.
|
| - */
|
| -Mosaic.Density.prototype.increase = function() {
|
| - if (this.horizontal == Mosaic.Density.MIN_HORIZONTAL ||
|
| - this.vertical == Mosaic.Density.MAX_VERTICAL) {
|
| - console.assert(this.horizontal < Mosaic.Density.MAX_HORIZONTAL);
|
| - this.horizontal++;
|
| - this.vertical = Mosaic.Density.MIN_VERTICAL;
|
| - } else {
|
| - this.vertical++;
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Decrease horizontal density.
|
| - */
|
| -Mosaic.Density.prototype.decreaseHorizontal = function() {
|
| - console.assert(this.horizontal > Mosaic.Density.MIN_HORIZONTAL);
|
| - this.horizontal--;
|
| -};
|
| -
|
| -/**
|
| - * @param {number} tileCount Number of tiles in the row.
|
| - * @param {number} rowIndex Global row index.
|
| - * @return {boolean} True if the row is complete.
|
| - */
|
| -Mosaic.Density.prototype.isRowComplete = function(tileCount, rowIndex) {
|
| - return (tileCount == this.horizontal) || (rowIndex % this.vertical) == 0;
|
| -};
|
| -
|
| -////////////////////////////////////////////////////////////////////////////////
|
| -
|
| -/**
|
| - * A column in a mosaic layout. Contains rows.
|
| - *
|
| - * @param {number} index Column index.
|
| - * @param {number} firstRowIndex Global row index.
|
| - * @param {number} firstTileIndex Index of the first tile in the column.
|
| - * @param {number} left Left edge coordinate.
|
| - * @param {number} maxHeight Maximum height.
|
| - * @param {Mosaic.Density} density Layout density.
|
| - * @constructor
|
| - */
|
| -Mosaic.Column = function(index, firstRowIndex, firstTileIndex, left, maxHeight,
|
| - density) {
|
| - this.index_ = index;
|
| - this.firstRowIndex_ = firstRowIndex;
|
| - this.firstTileIndex_ = firstTileIndex;
|
| - this.left_ = left;
|
| - this.maxHeight_ = maxHeight;
|
| - this.density_ = density;
|
| -
|
| - this.reset_();
|
| -};
|
| -
|
| -/**
|
| - * Reset the layout.
|
| - * @private
|
| - */
|
| -Mosaic.Column.prototype.reset_ = function() {
|
| - this.tiles_ = [];
|
| - this.rows_ = [];
|
| - this.newRow_ = null;
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Number of tiles in the column.
|
| - */
|
| -Mosaic.Column.prototype.getTileCount = function() { return this.tiles_.length };
|
| -
|
| -/**
|
| - * @return {number} Index of the last tile + 1.
|
| - */
|
| -Mosaic.Column.prototype.getNextTileIndex = function() {
|
| - return this.firstTileIndex_ + this.getTileCount();
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Global index of the last row + 1.
|
| - */
|
| -Mosaic.Column.prototype.getNextRowIndex = function() {
|
| - return this.firstRowIndex_ + this.rows_.length;
|
| -};
|
| -
|
| -/**
|
| - * @return {Array.<Mosaic.Tile>} Array of tiles in the column.
|
| - */
|
| -Mosaic.Column.prototype.getTiles = function() { return this.tiles_ };
|
| -
|
| -/**
|
| - * @param {number} index Tile index.
|
| - * @return {boolean} True if this column contains the tile with the given index.
|
| - */
|
| -Mosaic.Column.prototype.hasTile = function(index) {
|
| - return this.firstTileIndex_ <= index &&
|
| - index < (this.firstTileIndex_ + this.getTileCount());
|
| -};
|
| -
|
| -/**
|
| - * @param {number} y Y coordinate.
|
| - * @param {number} direction -1 for left, 1 for right.
|
| - * @return {number} Index of the tile lying on the edge of the column at the
|
| - * given y coordinate.
|
| - * @private
|
| - */
|
| -Mosaic.Column.prototype.getEdgeTileIndex_ = function(y, direction) {
|
| - for (var r = 0; r < this.rows_.length; r++) {
|
| - if (this.rows_[r].coversY(y))
|
| - return this.rows_[r].getEdgeTileIndex_(direction);
|
| - }
|
| - return -1;
|
| -};
|
| -
|
| -/**
|
| - * @param {number} index Tile index.
|
| - * @return {Mosaic.Row} The row containing the tile with a given index.
|
| - */
|
| -Mosaic.Column.prototype.getRowByTileIndex = function(index) {
|
| - for (var r = 0; r != this.rows_.length; r++)
|
| - if (this.rows_[r].hasTile(index))
|
| - return this.rows_[r];
|
| -
|
| - return null;
|
| -};
|
| -
|
| -/**
|
| - * Add a tile to the column.
|
| - *
|
| - * @param {Mosaic.Tile} tile The tile to add.
|
| - */
|
| -Mosaic.Column.prototype.add = function(tile) {
|
| - var rowIndex = this.getNextRowIndex();
|
| -
|
| - if (!this.newRow_)
|
| - this.newRow_ = new Mosaic.Row(this.getNextTileIndex());
|
| -
|
| - this.tiles_.push(tile);
|
| - this.newRow_.add(tile);
|
| -
|
| - if (this.density_.isRowComplete(this.newRow_.getTileCount(), rowIndex)) {
|
| - this.rows_.push(this.newRow_);
|
| - this.newRow_ = null;
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Prepare the column layout.
|
| - *
|
| - * @param {boolean=} opt_force True if the layout must be performed even for an
|
| - * incomplete column.
|
| - * @return {boolean} True if the layout was performed.
|
| - */
|
| -Mosaic.Column.prototype.prepareLayout = function(opt_force) {
|
| - if (opt_force && this.newRow_) {
|
| - this.rows_.push(this.newRow_);
|
| - this.newRow_ = null;
|
| - }
|
| -
|
| - if (this.rows_.length == 0)
|
| - return false;
|
| -
|
| - this.width_ = Math.min.apply(
|
| - null, this.rows_.map(function(row) { return row.getMaxWidth() }));
|
| -
|
| - this.height_ = 0;
|
| -
|
| - this.rowHeights_ = [];
|
| - for (var r = 0; r != this.rows_.length; r++) {
|
| - var rowHeight = this.rows_[r].getHeightForWidth(this.width_);
|
| - this.height_ += rowHeight;
|
| - this.rowHeights_.push(rowHeight);
|
| - }
|
| -
|
| - var overflow = this.height_ / this.maxHeight_;
|
| - if (!opt_force && (overflow < 1))
|
| - return false;
|
| -
|
| - if (overflow > 1) {
|
| - // Scale down the column width and height.
|
| - this.width_ = Math.round(this.width_ / overflow);
|
| - this.height_ = this.maxHeight_;
|
| - Mosaic.Layout.rescaleSizesToNewTotal(this.rowHeights_, this.maxHeight_);
|
| - }
|
| -
|
| - return true;
|
| -};
|
| -
|
| -/**
|
| - * Retry the column layout with less tiles per row.
|
| - */
|
| -Mosaic.Column.prototype.retryWithLowerDensity = function() {
|
| - this.density_.decreaseHorizontal();
|
| - this.reset_();
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Column left edge coordinate.
|
| - */
|
| -Mosaic.Column.prototype.getLeft = function() { return this.left_ };
|
| -
|
| -/**
|
| - * @return {number} Column right edge coordinate after the layout.
|
| - */
|
| -Mosaic.Column.prototype.getRight = function() {
|
| - return this.left_ + this.width_;
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Column height after the layout.
|
| - */
|
| -Mosaic.Column.prototype.getHeight = function() { return this.height_ };
|
| -
|
| -/**
|
| - * Perform the column layout.
|
| - * @param {number=} opt_offsetX Horizontal offset.
|
| - * @param {number=} opt_offsetY Vertical offset.
|
| - */
|
| -Mosaic.Column.prototype.layout = function(opt_offsetX, opt_offsetY) {
|
| - opt_offsetX = opt_offsetX || 0;
|
| - opt_offsetY = opt_offsetY || 0;
|
| - var rowTop = Mosaic.Layout.PADDING_TOP;
|
| - for (var r = 0; r != this.rows_.length; r++) {
|
| - this.rows_[r].layout(
|
| - opt_offsetX + this.left_,
|
| - opt_offsetY + rowTop,
|
| - this.width_,
|
| - this.rowHeights_[r]);
|
| - rowTop += this.rowHeights_[r];
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Check if the column layout is too ugly to be displayed.
|
| - *
|
| - * @return {boolean} True if the layout is suboptimal.
|
| - */
|
| -Mosaic.Column.prototype.isSuboptimal = function() {
|
| - var tileCounts =
|
| - this.rows_.map(function(row) { return row.getTileCount() });
|
| -
|
| - var maxTileCount = Math.max.apply(null, tileCounts);
|
| - if (maxTileCount == 1)
|
| - return false; // Every row has exactly 1 tile, as optimal as it gets.
|
| -
|
| - var sizes =
|
| - this.tiles_.map(function(tile) { return tile.getMaxContentHeight() });
|
| -
|
| - // Ugly layout #1: all images are small and some are one the same row.
|
| - var allSmall = Math.max.apply(null, sizes) <= Mosaic.Tile.SMALL_IMAGE_SIZE;
|
| - if (allSmall)
|
| - return true;
|
| -
|
| - // Ugly layout #2: all images are large and none occupies an entire row.
|
| - var allLarge = Math.min.apply(null, sizes) > Mosaic.Tile.SMALL_IMAGE_SIZE;
|
| - var allCombined = Math.min.apply(null, tileCounts) != 1;
|
| - if (allLarge && allCombined)
|
| - return true;
|
| -
|
| - // Ugly layout #3: some rows have too many tiles for the resulting width.
|
| - if (this.width_ / maxTileCount < 100)
|
| - return true;
|
| -
|
| - return false;
|
| -};
|
| -
|
| -////////////////////////////////////////////////////////////////////////////////
|
| -
|
| -/**
|
| - * A row in a mosaic layout. Contains tiles.
|
| - *
|
| - * @param {number} firstTileIndex Index of the first tile in the row.
|
| - * @constructor
|
| - */
|
| -Mosaic.Row = function(firstTileIndex) {
|
| - this.firstTileIndex_ = firstTileIndex;
|
| - this.tiles_ = [];
|
| -};
|
| -
|
| -/**
|
| - * @param {Mosaic.Tile} tile The tile to add.
|
| - */
|
| -Mosaic.Row.prototype.add = function(tile) {
|
| - console.assert(this.getTileCount() < Mosaic.Density.MAX_HORIZONTAL);
|
| - this.tiles_.push(tile);
|
| -};
|
| -
|
| -/**
|
| - * @return {Array.<Mosaic.Tile>} Array of tiles in the row.
|
| - */
|
| -Mosaic.Row.prototype.getTiles = function() { return this.tiles_ };
|
| -
|
| -/**
|
| - * Get a tile by index.
|
| - * @param {number} index Tile index.
|
| - * @return {Mosaic.Tile} Requested tile or null if not found.
|
| - */
|
| -Mosaic.Row.prototype.getTileByIndex = function(index) {
|
| - if (!this.hasTile(index))
|
| - return null;
|
| - return this.tiles_[index - this.firstTileIndex_];
|
| -};
|
| -
|
| -/**
|
| - *
|
| - * @return {number} Number of tiles in the row.
|
| - */
|
| -Mosaic.Row.prototype.getTileCount = function() { return this.tiles_.length };
|
| -
|
| -/**
|
| - * @param {number} index Tile index.
|
| - * @return {boolean} True if this row contains the tile with the given index.
|
| - */
|
| -Mosaic.Row.prototype.hasTile = function(index) {
|
| - return this.firstTileIndex_ <= index &&
|
| - index < (this.firstTileIndex_ + this.tiles_.length);
|
| -};
|
| -
|
| -/**
|
| - * @param {number} y Y coordinate.
|
| - * @return {boolean} True if this row covers the given Y coordinate.
|
| - */
|
| -Mosaic.Row.prototype.coversY = function(y) {
|
| - return this.top_ <= y && y < (this.top_ + this.height_);
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Y coordinate of the tile center.
|
| - */
|
| -Mosaic.Row.prototype.getCenterY = function() {
|
| - return this.top_ + Math.round(this.height_ / 2);
|
| -};
|
| -
|
| -/**
|
| - * Get the first or the last tile.
|
| - *
|
| - * @param {number} direction -1 for the first tile, 1 for the last tile.
|
| - * @return {number} Tile index.
|
| - * @private
|
| - */
|
| -Mosaic.Row.prototype.getEdgeTileIndex_ = function(direction) {
|
| - if (direction < 0)
|
| - return this.firstTileIndex_;
|
| - else
|
| - return this.firstTileIndex_ + this.getTileCount() - 1;
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Aspect ration of the combined content box of this row.
|
| - * @private
|
| - */
|
| -Mosaic.Row.prototype.getTotalContentAspectRatio_ = function() {
|
| - var sum = 0;
|
| - for (var t = 0; t != this.tiles_.length; t++)
|
| - sum += this.tiles_[t].getAspectRatio();
|
| - return sum;
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Total horizontal spacing in this row. This includes
|
| - * the spacing between the tiles and both left and right margins.
|
| - *
|
| - * @private
|
| - */
|
| -Mosaic.Row.prototype.getTotalHorizontalSpacing_ = function() {
|
| - return Mosaic.Layout.SPACING * this.getTileCount();
|
| -};
|
| -
|
| -/**
|
| - * @return {number} Maximum width that this row may have without overscaling
|
| - * any of the tiles.
|
| - */
|
| -Mosaic.Row.prototype.getMaxWidth = function() {
|
| - var contentHeight = Math.min.apply(null,
|
| - this.tiles_.map(function(tile) { return tile.getMaxContentHeight() }));
|
| -
|
| - var contentWidth =
|
| - Math.round(contentHeight * this.getTotalContentAspectRatio_());
|
| - return contentWidth + this.getTotalHorizontalSpacing_();
|
| -};
|
| -
|
| -/**
|
| - * Compute the height that best fits the supplied row width given
|
| - * aspect ratios of the tiles in this row.
|
| - *
|
| - * @param {number} width Row width.
|
| - * @return {number} Height.
|
| - */
|
| -Mosaic.Row.prototype.getHeightForWidth = function(width) {
|
| - var contentWidth = width - this.getTotalHorizontalSpacing_();
|
| - var contentHeight =
|
| - Math.round(contentWidth / this.getTotalContentAspectRatio_());
|
| - return contentHeight + Mosaic.Layout.SPACING;
|
| -};
|
| -
|
| -/**
|
| - * Position the row in the mosaic.
|
| - *
|
| - * @param {number} left Left position.
|
| - * @param {number} top Top position.
|
| - * @param {number} width Width.
|
| - * @param {number} height Height.
|
| - */
|
| -Mosaic.Row.prototype.layout = function(left, top, width, height) {
|
| - this.top_ = top;
|
| - this.height_ = height;
|
| -
|
| - var contentWidth = width - this.getTotalHorizontalSpacing_();
|
| - var contentHeight = height - Mosaic.Layout.SPACING;
|
| -
|
| - var tileContentWidth = this.tiles_.map(
|
| - function(tile) { return tile.getAspectRatio() });
|
| -
|
| - Mosaic.Layout.rescaleSizesToNewTotal(tileContentWidth, contentWidth);
|
| -
|
| - var tileLeft = left;
|
| - for (var t = 0; t != this.tiles_.length; t++) {
|
| - var tileWidth = tileContentWidth[t] + Mosaic.Layout.SPACING;
|
| - this.tiles_[t].layout(tileLeft, top, tileWidth, height);
|
| - tileLeft += tileWidth;
|
| - }
|
| -};
|
| -
|
| -////////////////////////////////////////////////////////////////////////////////
|
| -
|
| -/**
|
| - * A single tile of the image mosaic.
|
| - *
|
| - * @param {Element} container Container element.
|
| - * @param {Gallery.Item} item Gallery item associated with this tile.
|
| - * @return {Element} The new tile element.
|
| - * @constructor
|
| - */
|
| -Mosaic.Tile = function(container, item) {
|
| - var self = container.ownerDocument.createElement('div');
|
| - Mosaic.Tile.decorate(self, container, item);
|
| - return self;
|
| -};
|
| -
|
| -/**
|
| - * @param {Element} self Self pointer.
|
| - * @param {Element} container Container element.
|
| - * @param {Gallery.Item} item Gallery item associated with this tile.
|
| - */
|
| -Mosaic.Tile.decorate = function(self, container, item) {
|
| - self.__proto__ = Mosaic.Tile.prototype;
|
| - self.className = 'mosaic-tile';
|
| -
|
| - self.container_ = container;
|
| - self.item_ = item;
|
| - self.left_ = null; // Mark as not laid out.
|
| -};
|
| -
|
| -/**
|
| - * Load mode for the tile's image.
|
| - * @enum {number}
|
| - */
|
| -Mosaic.Tile.LoadMode = {
|
| - LOW_DPI: 0,
|
| - HIGH_DPI: 1
|
| -};
|
| -
|
| -/**
|
| -* Inherit from HTMLDivElement.
|
| -*/
|
| -Mosaic.Tile.prototype.__proto__ = HTMLDivElement.prototype;
|
| -
|
| -/**
|
| - * Minimum tile content size.
|
| - */
|
| -Mosaic.Tile.MIN_CONTENT_SIZE = 64;
|
| -
|
| -/**
|
| - * Maximum tile content size.
|
| - */
|
| -Mosaic.Tile.MAX_CONTENT_SIZE = 512;
|
| -
|
| -/**
|
| - * Default size for a tile with no thumbnail image.
|
| - */
|
| -Mosaic.Tile.GENERIC_ICON_SIZE = 128;
|
| -
|
| -/**
|
| - * Max size of an image considered to be 'small'.
|
| - * Small images are laid out slightly differently.
|
| - */
|
| -Mosaic.Tile.SMALL_IMAGE_SIZE = 160;
|
| -
|
| -/**
|
| - * @return {Gallery.Item} The Gallery item.
|
| - */
|
| -Mosaic.Tile.prototype.getItem = function() { return this.item_ };
|
| -
|
| -/**
|
| - * @return {number} Maximum content height that this tile can have.
|
| - */
|
| -Mosaic.Tile.prototype.getMaxContentHeight = function() {
|
| - return this.maxContentHeight_;
|
| -};
|
| -
|
| -/**
|
| - * @return {number} The aspect ratio of the tile image.
|
| - */
|
| -Mosaic.Tile.prototype.getAspectRatio = function() { return this.aspectRatio_ };
|
| -
|
| -/**
|
| - * @return {boolean} True if the tile is initialized.
|
| - */
|
| -Mosaic.Tile.prototype.isInitialized = function() {
|
| - return !!this.maxContentHeight_;
|
| -};
|
| -
|
| -/**
|
| - * Checks whether the image of specified (or better resolution) has been loaded.
|
| - *
|
| - * @param {Mosaic.Tile.LoadMode=} opt_loadMode Loading mode, default: LOW_DPI.
|
| - * @return {boolean} True if the tile is loaded with the specified dpi or
|
| - * better.
|
| - */
|
| -Mosaic.Tile.prototype.isLoaded = function(opt_loadMode) {
|
| - var loadMode = opt_loadMode || Mosaic.Tile.LoadMode.LOW_DPI;
|
| - switch (loadMode) {
|
| - case Mosaic.Tile.LoadMode.LOW_DPI:
|
| - if (this.imagePreloaded_ || this.imageLoaded_)
|
| - return true;
|
| - break;
|
| - case Mosaic.Tile.LoadMode.HIGH_DPI:
|
| - if (this.imageLoaded_)
|
| - return true;
|
| - break;
|
| - }
|
| - return false;
|
| -};
|
| -
|
| -/**
|
| - * Checks whether the image of specified (or better resolution) is being loaded.
|
| - *
|
| - * @param {Mosaic.Tile.LoadMode=} opt_loadMode Loading mode, default: LOW_DPI.
|
| - * @return {boolean} True if the tile is being loaded with the specified dpi or
|
| - * better.
|
| - */
|
| -Mosaic.Tile.prototype.isLoading = function(opt_loadMode) {
|
| - var loadMode = opt_loadMode || Mosaic.Tile.LoadMode.LOW_DPI;
|
| - switch (loadMode) {
|
| - case Mosaic.Tile.LoadMode.LOW_DPI:
|
| - if (this.imagePreloading_ || this.imageLoading_)
|
| - return true;
|
| - break;
|
| - case Mosaic.Tile.LoadMode.HIGH_DPI:
|
| - if (this.imageLoading_)
|
| - return true;
|
| - break;
|
| - }
|
| - return false;
|
| -};
|
| -
|
| -/**
|
| - * Mark the tile as not loaded to prevent it from participating in the layout.
|
| - */
|
| -Mosaic.Tile.prototype.markUnloaded = function() {
|
| - this.maxContentHeight_ = 0;
|
| - if (this.thumbnailLoader_) {
|
| - this.thumbnailLoader_.cancel();
|
| - this.imagePreloaded_ = false;
|
| - this.imagePreloading_ = false;
|
| - this.imageLoaded_ = false;
|
| - this.imageLoading_ = false;
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Initializes the thumbnail in the tile. Does not load an image, but sets
|
| - * target dimensions using metadata.
|
| - *
|
| - * @param {Object} metadata Metadata object.
|
| - * @param {function()} onImageMeasured Image measured callback.
|
| - */
|
| -Mosaic.Tile.prototype.init = function(metadata, onImageMeasured) {
|
| - this.markUnloaded();
|
| - this.left_ = null; // Mark as not laid out.
|
| -
|
| - // Set higher priority for the selected elements to load them first.
|
| - var priority = this.getAttribute('selected') ? 2 : 3;
|
| -
|
| - // Use embedded thumbnails on Drive, since they have higher resolution.
|
| - var hidpiEmbedded = FileType.isOnDrive(this.getItem().getUrl());
|
| - this.thumbnailLoader_ = new ThumbnailLoader(
|
| - this.getItem().getUrl(),
|
| - ThumbnailLoader.LoaderType.CANVAS,
|
| - metadata,
|
| - undefined, // Media type.
|
| - hidpiEmbedded ? ThumbnailLoader.UseEmbedded.USE_EMBEDDED :
|
| - ThumbnailLoader.UseEmbedded.NO_EMBEDDED,
|
| - priority);
|
| -
|
| - // If no hidpi embedded thumbnail available, then use the low resolution
|
| - // for preloading.
|
| - if (!hidpiEmbedded) {
|
| - this.thumbnailPreloader_ = new ThumbnailLoader(
|
| - this.getItem().getUrl(),
|
| - ThumbnailLoader.LoaderType.CANVAS,
|
| - metadata,
|
| - undefined, // Media type.
|
| - ThumbnailLoader.UseEmbedded.USE_EMBEDDED,
|
| - 2); // Preloaders have always higher priotity, so the preload images
|
| - // are loaded as soon as possible.
|
| - }
|
| -
|
| - var setDimensions = function(width, height) {
|
| - if (width > height) {
|
| - if (width > Mosaic.Tile.MAX_CONTENT_SIZE) {
|
| - height = Math.round(height * Mosaic.Tile.MAX_CONTENT_SIZE / width);
|
| - width = Mosaic.Tile.MAX_CONTENT_SIZE;
|
| - }
|
| - } else {
|
| - if (height > Mosaic.Tile.MAX_CONTENT_SIZE) {
|
| - width = Math.round(width * Mosaic.Tile.MAX_CONTENT_SIZE / height);
|
| - height = Mosaic.Tile.MAX_CONTENT_SIZE;
|
| - }
|
| - }
|
| - this.maxContentHeight_ = Math.max(Mosaic.Tile.MIN_CONTENT_SIZE, height);
|
| - this.aspectRatio_ = width / height;
|
| - onImageMeasured();
|
| - }.bind(this);
|
| -
|
| - // Dimensions are always acquired from the metadata. For local files, it is
|
| - // extracted from headers. For Drive files, it is received via the Drive API.
|
| - // If the dimensions are not available, then the fallback dimensions will be
|
| - // used (same as for the generic icon).
|
| - if (metadata.media && metadata.media.width) {
|
| - setDimensions(metadata.media.width, metadata.media.height);
|
| - } else if (metadata.drive && metadata.drive.imageWidth &&
|
| - metadata.drive.imageHeight) {
|
| - setDimensions(metadata.drive.imageWidth, metadata.drive.imageHeight);
|
| - } else {
|
| - // No dimensions in metadata, then use the generic dimensions.
|
| - setDimensions(Mosaic.Tile.GENERIC_ICON_SIZE,
|
| - Mosaic.Tile.GENERIC_ICON_SIZE);
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Loads an image into the tile.
|
| - *
|
| - * The mode argument is a hint. Use low-dpi for faster response, and high-dpi
|
| - * for better output, but possibly affecting performance.
|
| - *
|
| - * If the mode is high-dpi, then a the high-dpi image is loaded, but also
|
| - * low-dpi image is loaded for preloading (if available).
|
| - * For the low-dpi mode, only low-dpi image is loaded. If not available, then
|
| - * the high-dpi image is loaded as a fallback.
|
| - *
|
| - * @param {Mosaic.Tile.LoadMode} loadMode Loading mode.
|
| - * @param {function(boolean)} onImageLoaded Callback when image is loaded.
|
| - * The argument is true for success, false for failure.
|
| - */
|
| -Mosaic.Tile.prototype.load = function(loadMode, onImageLoaded) {
|
| - // Attaches the image to the tile and finalizes loading process for the
|
| - // specified loader.
|
| - var finalizeLoader = function(mode, success, loader) {
|
| - if (success && this.wrapper_) {
|
| - // Show the fade-in animation only when previously there was no image
|
| - // attached in this tile.
|
| - if (!this.imageLoaded_ && !this.imagePreloaded_)
|
| - this.wrapper_.classList.add('animated');
|
| - else
|
| - this.wrapper_.classList.remove('animated');
|
| - }
|
| - loader.attachImage(this.wrapper_, ThumbnailLoader.FillMode.OVER_FILL);
|
| - onImageLoaded(success);
|
| - switch (mode) {
|
| - case Mosaic.Tile.LoadMode.LOW_DPI:
|
| - this.imagePreloading_ = false;
|
| - this.imagePreloaded_ = true;
|
| - break;
|
| - case Mosaic.Tile.LoadMode.HIGH_DPI:
|
| - this.imageLoading_ = false;
|
| - this.imageLoaded_ = true;
|
| - break;
|
| - }
|
| - }.bind(this);
|
| -
|
| - // Always load the low-dpi image first if it is available for the fastest
|
| - // feedback.
|
| - if (!this.imagePreloading_ && this.thumbnailPreloader_) {
|
| - this.imagePreloading_ = true;
|
| - this.thumbnailPreloader_.loadDetachedImage(function(success) {
|
| - // Hi-dpi loaded first, ignore this call then.
|
| - if (this.imageLoaded_)
|
| - return;
|
| - finalizeLoader(Mosaic.Tile.LoadMode.LOW_DPI,
|
| - success,
|
| - this.thumbnailPreloader_);
|
| - }.bind(this));
|
| - }
|
| -
|
| - // Load the high-dpi image only when it is requested, or the low-dpi is not
|
| - // available.
|
| - if (!this.imageLoading_ &&
|
| - (loadMode == Mosaic.Tile.LoadMode.HIGH_DPI || !this.imagePreloading_)) {
|
| - this.imageLoading_ = true;
|
| - this.thumbnailLoader_.loadDetachedImage(function(success) {
|
| - // Cancel preloading, since the hi-dpi image is ready.
|
| - if (this.thumbnailPreloader_)
|
| - this.thumbnailPreloader_.cancel();
|
| - finalizeLoader(Mosaic.Tile.LoadMode.HIGH_DPI,
|
| - success,
|
| - this.thumbnailLoader_);
|
| - }.bind(this));
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * Unloads an image from the tile.
|
| - */
|
| -Mosaic.Tile.prototype.unload = function() {
|
| - this.thumbnailLoader_.cancel();
|
| - if (this.thumbnailPreloader_)
|
| - this.thumbnailPreloader_.cancel();
|
| - this.imagePreloaded_ = false;
|
| - this.imageLoaded_ = false;
|
| - this.imagePreloading_ = false;
|
| - this.imageLoading_ = false;
|
| - this.wrapper_.innerText = '';
|
| -};
|
| -
|
| -/**
|
| - * Select/unselect the tile.
|
| - *
|
| - * @param {boolean} on True if selected.
|
| - */
|
| -Mosaic.Tile.prototype.select = function(on) {
|
| - if (on)
|
| - this.setAttribute('selected', true);
|
| - else
|
| - this.removeAttribute('selected');
|
| -};
|
| -
|
| -/**
|
| - * Position the tile in the mosaic.
|
| - *
|
| - * @param {number} left Left position.
|
| - * @param {number} top Top position.
|
| - * @param {number} width Width.
|
| - * @param {number} height Height.
|
| - */
|
| -Mosaic.Tile.prototype.layout = function(left, top, width, height) {
|
| - this.left_ = left;
|
| - this.top_ = top;
|
| - this.width_ = width;
|
| - this.height_ = height;
|
| -
|
| - this.style.left = left + 'px';
|
| - this.style.top = top + 'px';
|
| - this.style.width = width + 'px';
|
| - this.style.height = height + 'px';
|
| -
|
| - if (!this.wrapper_) { // First time, create DOM.
|
| - this.container_.appendChild(this);
|
| - var border = util.createChild(this, 'img-border');
|
| - this.wrapper_ = util.createChild(border, 'img-wrapper');
|
| - }
|
| - if (this.hasAttribute('selected'))
|
| - this.scrollIntoView(false);
|
| -
|
| - if (this.imageLoaded_) {
|
| - this.thumbnailLoader_.attachImage(this.wrapper_,
|
| - ThumbnailLoader.FillMode.FILL);
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * If the tile is not fully visible scroll the parent to make it fully visible.
|
| - * @param {boolean=} opt_animated True, if scroll should be animated,
|
| - * default: true.
|
| - */
|
| -Mosaic.Tile.prototype.scrollIntoView = function(opt_animated) {
|
| - if (this.left_ == null) // Not laid out.
|
| - return;
|
| -
|
| - var targetPosition;
|
| - var tileLeft = this.left_ - Mosaic.Layout.SCROLL_MARGIN;
|
| - if (tileLeft < this.container_.scrollLeft) {
|
| - targetPosition = tileLeft;
|
| - } else {
|
| - var tileRight = this.left_ + this.width_ + Mosaic.Layout.SCROLL_MARGIN;
|
| - var scrollRight = this.container_.scrollLeft + this.container_.clientWidth;
|
| - if (tileRight > scrollRight)
|
| - targetPosition = tileRight - this.container_.clientWidth;
|
| - }
|
| -
|
| - if (targetPosition) {
|
| - if (opt_animated === false)
|
| - this.container_.scrollLeft = targetPosition;
|
| - else
|
| - this.container_.animatedScrollTo(targetPosition);
|
| - }
|
| -};
|
| -
|
| -/**
|
| - * @return {Rect} Rectangle occupied by the tile's image,
|
| - * relative to the viewport.
|
| - */
|
| -Mosaic.Tile.prototype.getImageRect = function() {
|
| - if (this.left_ == null) // Not laid out.
|
| - return null;
|
| -
|
| - var margin = Mosaic.Layout.SPACING / 2;
|
| - return new Rect(this.left_ - this.container_.scrollLeft, this.top_,
|
| - this.width_, this.height_).inflate(-margin, -margin);
|
| -};
|
| -
|
| -/**
|
| - * @return {number} X coordinate of the tile center.
|
| - */
|
| -Mosaic.Tile.prototype.getCenterX = function() {
|
| - return this.left_ + Math.round(this.width_ / 2);
|
| -};
|
|
|