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