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
5cr.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