1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 cr.define('ntp4', function() { 6 'use strict'; 7 8 /** 9 * Gives the proportion of the row width that is devoted to a single icon. 10 * @param {number} rowTileCount The number of tiles in a row. 11 * @return {number} The ratio between icon width and row width. 12 */ 13 function tileWidthFraction(rowTileCount) { 14 return rowTileCount + 15 (rowTileCount - 1) * TILE_SPACING_FRACTION; 16 } 17 18 /** 19 * Calculates an assortment of tile-related values for a grid with the 20 * given dimensions. 21 * @param {number} width The pixel width of the grid. 22 * @param {number} numRowTiles The number of tiles in a row. 23 * @return {Object} A mapping of pixel values. 24 */ 25 function tileValuesForGrid(width, numRowTiles) { 26 var tileWidth = width / tileWidthFraction(numRowTiles); 27 var offsetX = tileWidth * (1 + TILE_SPACING_FRACTION); 28 var interTileSpacing = offsetX - tileWidth; 29 30 return { 31 tileWidth: tileWidth, 32 offsetX: offsetX, 33 interTileSpacing: interTileSpacing, 34 }; 35 } 36 37 // The proportion of the tile width which will be used as spacing between 38 // tiles. 39 var TILE_SPACING_FRACTION = 1 / 8; 40 41 // The smallest amount of horizontal blank space to display on the sides when 42 // displaying a wide arrangement. 43 var MIN_WIDE_MARGIN = 100; 44 45 /** 46 * Creates a new TilePage object. This object contains tiles and controls 47 * their layout. 48 * @param {string} name The display name for the page. 49 * @param {Object} gridValues Pixel values that define the size and layout 50 * of the tile grid. 51 * @constructor 52 * @extends {HTMLDivElement} 53 */ 54 function TilePage(name, gridValues) { 55 var el = cr.doc.createElement('div'); 56 el.pageName = name; 57 el.gridValues_ = gridValues; 58 el.__proto__ = TilePage.prototype; 59 el.initialize(); 60 61 return el; 62 } 63 64 /** 65 * Takes a collection of grid layout pixel values and updates them with 66 * additional tiling values that are calculated from TilePage constants. 67 * @param {Object} grid The grid layout pixel values to update. 68 */ 69 TilePage.initGridValues = function(grid) { 70 // The amount of space we need to display a narrow grid (all narrow grids 71 // are this size). 72 grid.narrowWidth = 73 grid.minTileWidth * tileWidthFraction(grid.minColCount); 74 // The minimum amount of space we need to display a wide grid. 75 grid.minWideWidth = 76 grid.minTileWidth * tileWidthFraction(grid.maxColCount); 77 // The largest we will ever display a wide grid. 78 grid.maxWideWidth = 79 grid.maxTileWidth * tileWidthFraction(grid.maxColCount); 80 // Tile-related pixel values for the narrow display. 81 grid.narrowTileValues = tileValuesForGrid(grid.narrowWidth, 82 grid.minColCount); 83 // Tile-related pixel values for the minimum narrow display. 84 grid.wideTileValues = tileValuesForGrid(grid.minWideWidth, 85 grid.maxColCount); 86 }, 87 88 TilePage.prototype = { 89 __proto__: HTMLDivElement.prototype, 90 91 initialize: function() { 92 this.className = 'tile-page'; 93 94 var title = this.ownerDocument.createElement('span'); 95 title.textContent = this.pageName; 96 title.className = 'tile-page-title'; 97 this.appendChild(title); 98 99 // Div that holds the tiles. 100 this.tileGrid_ = this.ownerDocument.createElement('div'); 101 this.tileGrid_.className = 'tile-grid'; 102 this.appendChild(this.tileGrid_); 103 104 // Ordered list of our tiles. 105 this.tileElements_ = this.tileGrid_.getElementsByClassName('tile'); 106 107 this.lastWidth_ = this.clientWidth; 108 109 this.eventTracker = new EventTracker(); 110 this.eventTracker.add(window, 'resize', this.onResize_.bind(this)); 111 }, 112 113 /** 114 * Cleans up resources that are no longer needed after this TilePage 115 * instance is removed from the DOM. 116 */ 117 tearDown: function() { 118 this.eventTracker.removeAll(); 119 }, 120 121 /** 122 * @protected 123 */ 124 appendTile: function(tileElement) { 125 var wrapperDiv = tileElement.ownerDocument.createElement('div'); 126 wrapperDiv.appendChild(tileElement); 127 wrapperDiv.className = 'tile'; 128 this.tileGrid_.appendChild(wrapperDiv); 129 130 this.positionTile_(this.tileElements_.length - 1); 131 this.classList.remove('resizing-tile-page'); 132 }, 133 134 /** 135 * Calculates the x/y coordinates for an element and moves it there. 136 * @param {number} The index of the element to be positioned. 137 * @private 138 */ 139 positionTile_: function(index) { 140 var grid = this.gridValues_; 141 142 var availableSpace = this.tileGrid_.clientWidth - 2 * MIN_WIDE_MARGIN; 143 var wide = availableSpace >= grid.minWideWidth; 144 // Calculate the portion of the tile's position that should be animated. 145 var animatedTileValues = wide ? 146 grid.wideTileValues : grid.narrowTileValues; 147 // Animate the difference between three-wide and six-wide. 148 var animatedLeftMargin = wide ? 149 0 : (grid.minWideWidth - MIN_WIDE_MARGIN - grid.narrowWidth) / 2; 150 151 var numRowTiles = wide ? grid.maxColCount : grid.minColCount; 152 var col = index % numRowTiles; 153 var row = Math.floor(index / numRowTiles); 154 var animatedX = col * animatedTileValues.offsetX + animatedLeftMargin; 155 var animatedY = row * (this.heightForWidth(animatedTileValues.tileWidth) + 156 animatedTileValues.interTileSpacing); 157 158 // Calculate the final on-screen position for the tile. 159 var effectiveGridWidth = wide ? 160 Math.min(Math.max(availableSpace, grid.minWideWidth), 161 grid.maxWideWidth) : 162 grid.narrowWidth; 163 var realTileValues = tileValuesForGrid(effectiveGridWidth, numRowTiles); 164 // leftMargin centers the grid within the avaiable space. 165 var minMargin = wide ? MIN_WIDE_MARGIN : 0; 166 var leftMargin = 167 Math.max(minMargin, 168 (this.tileGrid_.clientWidth - effectiveGridWidth) / 2); 169 var realX = col * realTileValues.offsetX + leftMargin; 170 var realY = row * (this.heightForWidth(realTileValues.tileWidth) + 171 realTileValues.interTileSpacing); 172 173 var tileWrapper = this.tileElements_[index]; 174 tileWrapper.style.left = animatedX + 'px'; 175 tileWrapper.style.top = animatedY + 'px'; 176 tileWrapper.firstChild.setBounds(realTileValues.tileWidth, 177 realX - animatedX, realY - animatedY); 178 }, 179 180 /** 181 * Window resize event handler. Window resizes may trigger re-layouts. 182 * @param {Object} e The resize event. 183 */ 184 onResize_: function(e) { 185 // Do nothing if the width didn't change. 186 if (this.lastWidth_ == this.clientWidth) 187 return; 188 189 this.lastWidth_ = this.clientWidth; 190 this.classList.add('resizing-tile-page'); 191 192 for (var i = 0; i < this.tileElements_.length; i++) { 193 this.positionTile_(i); 194 } 195 }, 196 197 /** 198 * Get the height for a tile of a certain width. Override this function to 199 * get non-square tiles. 200 * @param {number} width The pixel width of a tile. 201 * @return {number} The height for |width|. 202 */ 203 heightForWidth: function(width) { 204 return width; 205 }, 206 }; 207 208 return { 209 TilePage: TilePage, 210 }; 211 }); 212