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 */ 35 WebInspector.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 */ 53 WebInspector.ViewportControl.Provider = function() 54 { 55 } 56 57 WebInspector.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 70 WebInspector.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