• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @constructor
33 * @param {!WebInspector.ViewportControl.Provider} provider
34 */
35WebInspector.ViewportControl = function(provider)
36{
37    this.element = document.createElement("div");
38    this.element.className = "fill";
39    this.element.style.overflow = "auto";
40    this._topGapElement = this.element.createChild("div");
41    this._contentElement = this.element.createChild("div");
42    this._bottomGapElement = this.element.createChild("div");
43
44    this._provider = provider;
45    this.element.addEventListener("scroll", this._onScroll.bind(this), false);
46    this._firstVisibleIndex = 0;
47    this._lastVisibleIndex = -1;
48}
49
50/**
51 * @interface
52 */
53WebInspector.ViewportControl.Provider = function()
54{
55}
56
57WebInspector.ViewportControl.Provider.prototype = {
58    /**
59     * @return {number}
60     */
61    itemCount: function() { return 0; },
62
63    /**
64     * @param {number} index
65     * @return {?Element}
66     */
67    itemElement: function(index) { return null; }
68}
69
70WebInspector.ViewportControl.prototype = {
71    /**
72     * @return {!Element}
73     */
74    contentElement: function()
75    {
76        return this._contentElement;
77    },
78
79    refresh: function()
80    {
81        if (!this.element.clientHeight)
82            return;  // Do nothing for invisible controls.
83
84        // Secure scroller.
85        this._contentElement.style.setProperty("height", "100000px");
86        this._contentElement.removeChildren();
87        var itemCount = this._provider.itemCount();
88        if (!itemCount) {
89            this._firstVisibleIndex = -1;
90            this._lastVisibleIndex = -1;
91            return;
92        }
93
94        if (!this._rowHeight) {
95            var firstElement = this._provider.itemElement(0);
96            this._rowHeight = firstElement.measurePreferredSize(this._contentElement).height;
97        }
98
99        var visibleFrom = this.element.scrollTop;
100        var visibleTo = visibleFrom + this.element.clientHeight;
101
102        this._firstVisibleIndex = Math.floor(visibleFrom / this._rowHeight);
103        this._lastVisibleIndex = Math.min(Math.ceil(visibleTo / this._rowHeight), itemCount) - 1;
104
105        this._topGapElement.style.height = (this._rowHeight * this._firstVisibleIndex) + "px";
106        this._bottomGapElement.style.height = (this._rowHeight * (itemCount - this._lastVisibleIndex - 1)) + "px";
107
108        for (var i = this._firstVisibleIndex; i <= this._lastVisibleIndex; ++i)
109            this._contentElement.appendChild(this._provider.itemElement(i));
110        // Release scroller protection.
111        this._contentElement.style.removeProperty("height");
112    },
113
114    /**
115     * @param {?Event} event
116     */
117    _onScroll: function(event)
118    {
119        this.refresh();
120    },
121
122    /**
123     * @return {number}
124     */
125    rowsPerViewport: function()
126    {
127        return Math.floor(this.element.clientHeight / this._rowHeight);
128    },
129
130    /**
131     * @return {number}
132     */
133    firstVisibleIndex: function()
134    {
135        return this._firstVisibleIndex;
136    },
137
138    /**
139     * @return {number}
140     */
141    lastVisibleIndex: function()
142    {
143        return this._lastVisibleIndex;
144    },
145
146    /**
147     * @return {?Element}
148     */
149    renderedElementAt: function(index)
150    {
151        if (index < this._firstVisibleIndex)
152            return null;
153        if (index > this._lastVisibleIndex)
154            return null;
155        return this._contentElement.childNodes[index - this._firstVisibleIndex];
156    },
157
158    /**
159     * @param {number} index
160     * @param {boolean=} makeLast
161     */
162    scrollItemIntoView: function(index, makeLast)
163    {
164        if (index > this._firstVisibleIndex && index < this._lastVisibleIndex)
165            return;
166
167        if (makeLast)
168            this.element.scrollTop = this._rowHeight * (index + 1) - this.element.clientHeight;
169        else
170            this.element.scrollTop = this._rowHeight * index;
171    }
172}
173