1 goog.provide('lime.ui.Scroller'); 2 3 goog.require('lime.Sprite'); 4 goog.require('lime.animation.MoveTo'); 5 6 /** 7 * @constructor 8 * @extends lime.Sprite 9 */ 10 lime.ui.Scroller = function() { 11 12 goog.base(this); 13 14 //need default size for autoresize 15 this.setSize(100, 100); 16 17 this.clipper = new lime.Sprite().setFill('#c00').setSize(100, 100). 18 setAutoResize(lime.AutoResize.ALL); 19 this.appendChild(this.clipper); 20 this.setMask(this.clipper); 21 22 this.moving_ = new lime.Layer(); 23 lime.Node.prototype.appendChild.call(this, this.moving_); 24 25 goog.events.listen(this, ['mousedown', 'touchstart'], 26 this.downHandler_, false, this); 27 28 29 this.setDirection(lime.ui.Scroller.Direction.HORIZONTAL); 30 31 }; 32 goog.inherits(lime.ui.Scroller, lime.Sprite); 33 34 /** 35 * Offset that can be dragged over the edge 36 * @const 37 * @type number 38 */ 39 lime.ui.Scroller.OFFSET = 250; 40 41 /** 42 * Factor to slow down if over the edge 43 * @const 44 * @type number 45 */ 46 lime.ui.Scroller.OFFSET_LAG = .4; 47 48 /** 49 * How fast to slow down 50 * @const 51 * @type number 52 */ 53 lime.ui.Scroller.FRICTION = .95; 54 55 /** 56 * Directions of the scroller. 57 * @enum number 58 */ 59 lime.ui.Scroller.Direction = { 60 HORIZONTAL: 0, 61 VERTICAL: 1 62 }; 63 64 /** 65 * Returns the direction of the scroller (horizontal/vertical) 66 * @return {lime.ui.Scroller.Direction} Scroll direction. 67 */ 68 lime.ui.Scroller.prototype.getDirection = function() { 69 return this.direction_; 70 }; 71 72 /** 73 * Set the direction of the scroller (horizontal/vertical) 74 * @param {lime.ui.Scroller.Direction} direction Direction. 75 * @return {lime.ui.Scroller} object itself. 76 */ 77 lime.ui.Scroller.prototype.setDirection = function(direction) { 78 this.direction_ = direction; 79 return this; 80 }; 81 82 /** 83 * @inheritDoc 84 * @see lime.Node#setAnchorPoint 85 */ 86 lime.ui.Scroller.prototype.setAnchorPoint = function() { 87 if (this.clipper) { 88 this.clipper.setAnchorPoint.apply(this.clipper, arguments); 89 } 90 return lime.Node.prototype.setAnchorPoint.apply(this, arguments); 91 }; 92 93 /** 94 * @inheritDoc 95 * @see lime.Node#appendChild 96 */ 97 lime.ui.Scroller.prototype.appendChild = function() { 98 if (this.moving_) return this.moving_.appendChild.apply( 99 this.moving_, arguments); 100 return lime.Node.prototype.appendChild.apply(this, arguments); 101 }; 102 103 /** 104 * @inheritDoc 105 * @see lime.Node#removeChild 106 */ 107 lime.ui.Scroller.prototype.removeChild = function() { 108 if (this.moving_) return this.moving_.removeChild.apply( 109 this.moving_, arguments); 110 return lime.Node.prototype.removeChild.apply(this, arguments); 111 }; 112 113 /** 114 * Measure the contents of the scroller and set 115 * up high and low limits. 116 */ 117 lime.ui.Scroller.prototype.measureLimits = function() { 118 119 var measure = this.moving_.measureContents(); 120 if (this.getDirection() == lime.ui.Scroller.Direction.HORIZONTAL) { 121 this.downy = this.moving_.getPosition().y; 122 var max = this.getSize().width; 123 124 this.LOW = -measure.left; 125 this.HIGH = Math.min(max - measure.right, -measure.left); 126 var diff = (measure.right - measure.left) - max; 127 } 128 else { 129 this.downx = this.moving_.getPosition().x; 130 max = this.getSize().height; 131 132 this.LOW = -measure.top; 133 this.HIGH = Math.min(max - measure.bottom, -measure.top); 134 diff = (measure.bottom - measure.top) - max; 135 } 136 137 if (diff > 0) { 138 this.LOW -= diff; 139 this.HIGH += diff; 140 } 141 }; 142 143 /** 144 * Scroll to specific offset in pixels 145 * @param {number} offset Offset in pixels. 146 * @param {number=} opt_duration Animation duration. 147 */ 148 lime.ui.Scroller.prototype.scrollTo = function(offset, opt_duration) { 149 var pos = this.moving_.getPosition().clone(); 150 var duration = opt_duration || 0; 151 152 this.measureLimits(); 153 154 if (offset < 0) offset = 0; 155 156 if (this.getDirection() == lime.ui.Scroller.Direction.HORIZONTAL) { 157 pos.x = this.HIGH - offset; 158 if (pos.x < this.LOW) pos.x = this.LOW; 159 } 160 else { 161 pos.y = this.HIGH - offset; 162 if (pos.y < this.LOW) pos.y = this.LOW; 163 } 164 165 if (duration) { 166 this.moving_.runAction(new lime.animation.MoveTo(pos.x, pos.y). 167 setDuration(duration).enableOptimizations(). 168 setEasing(lime.animation.getEasingFunction(.19, .6, .35, .97))); 169 } 170 else this.moving_.setPosition(pos); 171 }; 172 173 /** 174 * Handle down events 175 * @private 176 * @param {lime.events.Event} e Event. 177 */ 178 lime.ui.Scroller.prototype.downHandler_ = function(e) { 179 if (this.ismove) return; 180 181 e.position = this.localToNode(e.position, this.moving_); 182 this.downx = e.position.x; 183 this.downy = e.position.y; 184 185 this.v = 0; 186 this.ismove = 1; 187 188 if (this.getDirection() == lime.ui.Scroller.Direction.HORIZONTAL) { 189 this.oldvalue = this.posvalue = this.moving_.getPosition().x; 190 } 191 else { 192 this.oldvalue = this.posvalue = this.moving_.getPosition().y; 193 } 194 195 this.measureLimits(); 196 197 lime.animation.actionManager.stopAll(this.moving_); 198 //this.moving_.setPosition(p); 199 200 lime.scheduleManager.schedule(this.captureVelocity_, this); 201 202 e.swallow(['touchmove', 'mousemove'], this.moveHandler_); 203 204 e.swallow(['touchend', 'mouseup', 'touchcancel'], this.upHandler_); 205 206 this.event = e.clone(); 207 }; 208 209 /** 210 * Capture the scrolling velocity to get some throwing 211 * motion after release. 212 * @private 213 */ 214 lime.ui.Scroller.prototype.captureVelocity_ = function() { 215 if (this.ismove) { 216 this.v = (this.posvalue - this.oldvalue) * 1.05; 217 this.oldvalue = this.posvalue; 218 } 219 this.v *= lime.ui.Scroller.FRICTION; 220 }; 221 222 /** 223 * Cancel all current movement events. 224 */ 225 lime.ui.Scroller.prototype.cancelEvents = function() { 226 if (this.event) { 227 this.event.release(); 228 } 229 this.ismove = 0; 230 }; 231 232 /** 233 * Handle move events. 234 * @private 235 * @param {lime.events.Event} e Event. 236 */ 237 lime.ui.Scroller.prototype.moveHandler_ = function(e) { 238 var pos = e.position.clone(), dir = this.getDirection(), activeval; 239 if (dir == lime.ui.Scroller.Direction.HORIZONTAL) { 240 pos.x -= this.downx; 241 pos.y = this.downy; 242 activeval = pos.x; 243 } 244 else { 245 pos.x = this.downx; 246 pos.y -= this.downy; 247 activeval = pos.y; 248 } 249 250 251 if (activeval < this.LOW) { 252 var diff = this.LOW - activeval; 253 254 if (diff > lime.ui.Scroller.OFFSET) diff = lime.ui.Scroller.OFFSET; 255 activeval = this.LOW - diff * lime.ui.Scroller.OFFSET_LAG; 256 } 257 if (activeval > this.HIGH) { 258 diff = activeval - this.HIGH; 259 260 if (diff > lime.ui.Scroller.OFFSET) diff = lime.ui.Scroller.OFFSET; 261 activeval = this.HIGH + diff * lime.ui.Scroller.OFFSET_LAG; 262 } 263 264 this.posvalue = activeval; 265 266 if (dir == lime.ui.Scroller.Direction.HORIZONTAL) { 267 pos.x = activeval; 268 } 269 else { 270 pos.y = activeval; 271 } 272 273 this.moving_.setPosition(pos); 274 275 276 }; 277 278 /** 279 * Handle release(up) events. 280 * @private 281 * @param {lime.events.Event} e Event. 282 */ 283 lime.ui.Scroller.prototype.upHandler_ = function(e) { 284 var pos = e.position.clone(), dir = this.getDirection(), activeval; 285 286 if (dir == lime.ui.Scroller.Direction.HORIZONTAL) { 287 pos.x -= this.downx; 288 pos.y = this.downy; 289 activeval = pos.x; 290 } 291 else { 292 pos.x = this.downx; 293 pos.y -= this.downy; 294 activeval = pos.y; 295 } 296 297 lime.scheduleManager.unschedule(this.captureVelocity_, this); 298 var k = Math.log(0.5 / Math.abs(this.v)) / 299 Math.log(lime.ui.Scroller.FRICTION), 300 duration = k / 30, 301 endpos = (Math.abs(this.v) * 302 (Math.pow(lime.ui.Scroller.FRICTION, k) - 1)) / 303 (lime.ui.Scroller.FRICTION - 1) * (this.v > 0 ? 1 : -1); 304 305 activeval += endpos; 306 this.ismove = 0; 307 308 if (this.v != 0) { 309 310 var diff = endpos; 311 312 if (activeval < this.LOW) { 313 diff = this.LOW - (activeval - endpos); 314 activeval = this.LOW; 315 } 316 if (activeval > this.HIGH) { 317 diff = this.HIGH - (activeval - endpos); 318 activeval = this.HIGH; 319 } 320 //console.log(diff,endpos); 321 duration *= (diff / endpos); 322 323 } 324 325 if (this.oldvalue < this.LOW) { 326 activeval = this.LOW; 327 duration = .3; 328 } 329 if (this.oldvalue > this.HIGH) { 330 activeval = this.HIGH; 331 duration = .3; 332 } 333 334 if (dir == lime.ui.Scroller.Direction.HORIZONTAL) { 335 pos.x = activeval; 336 } 337 else { 338 pos.y = activeval; 339 } 340 341 if (Math.abs(duration) < 10) { 342 this.moving_.runAction(new lime.animation.MoveTo(pos.x, pos.y). 343 setDuration(duration).enableOptimizations(). 344 setEasing(lime.animation.getEasingFunction(.19, .6, .35, .97))); 345 } 346 347 348 }; 349