3 function X_UI_ScrollBox_momentum( current, start, time, lowerMargin, wrapperSize, deceleration ){
\r
4 var distance = current - start,
\r
5 speed = Math.abs( distance ) / time,
\r
9 deceleration = deceleration === undefined ? 0.0006 : deceleration;
\r
11 destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
\r
12 duration = speed / deceleration;
\r
14 if( destination < lowerMargin ){
\r
15 destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin;
\r
16 distance = Math.abs( destination - current );
\r
17 duration = distance / speed;
\r
19 if ( destination > 0 ) {
\r
20 destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0;
\r
21 distance = Math.abs( current ) + destination;
\r
22 duration = distance / speed;
\r
26 destination : Math.round( destination ),
\r
31 var X_UI_ScrollBox_SUPPORT_ATTRS = {
\r
32 // スクロール開始するために必要な移動距離、縦か横、どちらか制限する場合、より重要
\r
33 directionLockThreshold : [ 10, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.LENGTH ],
\r
34 scrollX : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ],
\r
35 scrollY : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ],
\r
36 enabled : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ],
\r
37 bounce : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ],
\r
38 bounceTime : [ 600, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.TIME ],
\r
39 useWheel : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ],
\r
40 useKey : [ true, X.UI.Dirty.CLEAN, X.UI.Attr.USER.UINODE, X.UI.Attr.Type.BOOLEAN ],
\r
44 X.UI._ScrollBox = X.UI._ChromeBox.inherits(
\r
46 X_Class.PRIVATE_DATA | X_Class.SUPER_ACCESS,
\r
49 //elmScroller : null,
\r
50 //elmScrollbar : null,
\r
52 supportAttrs : X.UI.Attr.createAttrDef( X.UI.Attr.Support, X_UI_ScrollBox_SUPPORT_ATTRS ),
\r
62 directionLocked : '',
\r
65 isAnimating : false,
\r
86 lastScrollWidth : 0,
\r
87 lastScrollHeight : 0,
\r
91 _containerNode : null,
\r
92 scrollManager : null,
\r
94 Constructor : function( layout, args ){
\r
95 this[ 'Super' ]( layout, args );
\r
96 this._containerNode = X_Class_getPrivate( this.containerNode );
\r
99 creationComplete : function(){
\r
100 X.UI._Box.prototype.creationComplete.apply( this, arguments );
\r
101 this.scrollManager;
\r
105 calculate : function(){
\r
106 this.lastScrollWidth = this.scrollWidth;
\r
107 this.lastScrollHeight = this.scrollHeight;
\r
108 this.lastBoxWidth = this.boxWidth;
\r
109 this.lastBoxHeight = this.boxHeight;
\r
111 X.UI._Box.prototype.calculate.apply( this, arguments );
\r
114 this.lastScrollWidth !== this.scrollWidth || this.lastScrollHeight !== this.scrollHeight ||
\r
115 this.lastBoxWidth !== this.boxWidth || this.lastBoxHeight !== this.boxHeight
\r
117 // scroll の停止、GPU の解除
\r
123 scrollBy : function( x, y, opt_time, opt_easing ){
\r
124 this.scrollTo( this.x + x, this.y + y, opt_time, opt_easing );
\r
127 scrollTo : function( x, y, opt_time, opt_easing ){
\r
128 opt_time = 0 <= opt_time ? opt_time : 0;
\r
129 opt_easing = opt_easing || 'circular';
\r
131 this.isInTransition = !!opt_time;
\r
133 this.containerNode.animate(
\r
142 opt_time, opt_easing, 1000
\r
148 if( this.indicators ){
\r
149 for( i = this.indicators.length; i--; ){
\r
150 this.indicators[ i ].updatePosition( opt_time, opt_easing );
\r
155 _check : function(){
\r
156 var needVScroll, needHScroll;
\r
157 if( this.boxWidth < this._containerNode.scrollWidth || this.boxHeight < this._containerNode.scrollHeight ){
\r
159 if( this.scrolling ){
\r
160 // fix scroll position from scrollXPercent, scrollYPercent
\r
167 this[ 'listen' ]( X.UI.Event.POINTER_START, X_UI_ScrollBox_onStart );
\r
170 this._move( 0, 0 );
\r
172 this.scrolling = true;
\r
176 if( this.scrolling ){
\r
178 this[ 'unlisten' ]( X.UI.Event.POINTER_START );
\r
180 ( this._scrollX !== 0 || this._scrollY !== 0 ) && this._move( 0, 0 );
\r
182 delete this.scrolling;
\r
186 _move : function( x, y ){
\r
190 _remove : function(){
\r
191 X.UI._AbstractUINode.prototype._remove.apply( this, arguments );
\r
192 if( this.scrolling ){
\r
201 function X_UI_ScrollBox_onStart( e ){
\r
202 var ret = X_Callback_NONE;
\r
204 // React to left mouse button only
\r
205 if( e.pointerType === 'mouse' && e.button !== 0 ){
\r
209 if( !this.enabled || ( this.initiated && e.pointerType !== this.initiated ) ){
\r
213 this.initiated = e.pointerType;
\r
214 this.moved = false;
\r
217 this.directionX = 0;
\r
218 this.directionY = 0;
\r
219 this.directionLocked = '';
\r
220 this.startTime = X_Timer_now();
\r
223 if( this.isAnimating ){
\r
224 delete this.isAnimating;
\r
225 this[ 'dispatch' ]( X.UI.Event.SCROLL_END );
\r
228 this.startX = this.x;
\r
229 this.startY = this.y;
\r
230 this.absStartX = this.x;
\r
231 this.absStartY = this.y;
\r
232 this.pointX = e.pageX;
\r
233 this.pointY = e.pageY;
\r
235 this[ 'listen' ]( X.UI.Event.POINTER_MOVE, X_UI_ScrollBox_onMove );
\r
236 this[ 'listen' ]( X.UI.Event.POINTER_END , X_UI_ScrollBox_onEnd );
\r
238 //console.log( 'start : 3' );
\r
239 return ret | X_Callback_PREVENT_DEFAULT;
\r
242 function X_UI_ScrollBox_onMove( e ){
\r
243 var ret = X_Callback_NONE,
\r
244 deltaX, deltaY, timestamp,
\r
246 absDistX, absDistY;
\r
247 // 規定以上の move でスクロール開始
\r
249 if( !this.enabled || e.pointerType !== this.initiated ){
\r
254 if( !this.containerNode[ '_anime' ] ){
\r
255 console.log( 'gpuレイヤーの用意' );
\r
256 this._translate( this.x, this.y );
\r
260 deltaX = e.pageX - this.pointX;
\r
261 deltaY = e.pageY - this.pointY;
\r
262 timestamp = X_Timer_now();
\r
264 this.pointX = e.pageX;
\r
265 this.pointY = e.pageY;
\r
267 this.distX += deltaX;
\r
268 this.distY += deltaY;
\r
269 absDistX = Math.abs(this.distX);
\r
270 absDistY = Math.abs(this.distY);
\r
272 // We need to move at least 10 pixels for the scrolling to initiate
\r
273 if( 300 < timestamp - this.endTime && ( absDistX < 10 && absDistY < 10 ) ){
\r
277 // If you are scrolling in one direction lock the other
\r
278 if( !this.directionLocked ){
\r
279 if( absDistX > absDistY + this.directionLockThreshold ){
\r
280 this.directionLocked = 'h'; // lock horizontally
\r
282 if( absDistY >= absDistX + this.directionLockThreshold ){
\r
283 this.directionLocked = 'v'; // lock vertically
\r
285 this.directionLocked = 'n'; // no lock
\r
289 if( this.directionLocked === 'h' ){
\r
292 if( this.directionLocked === 'v' ){
\r
296 deltaX = this.hasHScroll ? deltaX : 0;
\r
297 deltaY = this.hasVScroll ? deltaY : 0;
\r
300 this[ 'dispatch' ]( X.UI.Event.SCROLL_BEFORE_MOVE );
\r
302 this.minusX = deltaX;
\r
303 this.minusY = deltaY;
\r
305 this[ 'dispatch' ]( X.UI.Event.SCROLL_MOVE );
\r
308 newX = this.x + deltaX;// - this.minusX;
\r
309 newY = this.y + deltaY;// - this.minusY;
\r
311 // Slow down if outside of the boundaries
\r
312 if( 0 < newX || newX < this.maxScrollX ){
\r
313 newX = this.bounce ? this.x + ( deltaX ) / 3 : 0 < newX ? 0 : this.maxScrollX;
\r
315 if( 0 < newY || newY < this.maxScrollY ){
\r
316 newY = this.bounce ? this.y + ( deltaY ) / 3 : 0 < newY ? 0 : this.maxScrollY;
\r
319 this.directionX = 0 < deltaX ? -1 : deltaX < 0 ? 1 : 0;
\r
320 this.directionY = 0 < deltaY ? -1 : deltaY < 0 ? 1 : 0;
\r
322 this._translate( newX, newY );
\r
324 if( 300 < timestamp - this.startTime ){
\r
325 this.startTime = timestamp;
\r
326 this.startX = this.x;
\r
327 this.startY = this.y;
\r
330 return ret | X_Callback_PREVENT_DEFAULT | X_Callback_MONOPOLY;
\r
333 function X_UI_ScrollBox_onEnd( e ){
\r
334 var ret = X_Callback_NONE,
\r
338 momentumX, momentumY,
\r
339 duration, distanceX, distanceY;
\r
341 this[ 'unlisten' ]( X.UI.Event.POINTER_MOVE, X_UI_ScrollBox_onMove );
\r
342 this[ 'unlisten' ]( X.UI.Event.POINTER_END, X_UI_ScrollBox_onEnd );
\r
344 if( !this.enabled || e.pointerType !== this.initiated ){
\r
348 delete this.isInTransition;
\r
349 delete this.initiated;
\r
350 this.endTime = X_Timer_now();
\r
352 duration = this.endTime - this.startTime;
\r
353 newX = Math.round( this.x );
\r
354 newY = Math.round( this.y );
\r
355 distanceX = Math.abs(newX - this.startX);
\r
356 distanceY = Math.abs(newY - this.startY);
\r
358 // reset if we are outside of the boundaries
\r
359 if( X_UI_ScrollBox_resetPosition( this, this.options.bounceTime ) ){
\r
363 // we scrolled less than 10 pixels
\r
365 // this[ 'dispatch' ]( X_EVENT_CANCELED );
\r
369 this.scrollTo( newX, newY, 0 ); // ensures that the last position is rounded
\r
371 // start momentum animation if needed
\r
372 if( this.options.momentum && duration < 300 ){
\r
373 momentumX = this.hasHScroll ?
\r
374 X_UI_ScrollBox_momentum( this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0, this.options.deceleration ) :{ destination: newX, duration: 0 };
\r
375 momentumY = this.hasVScroll ?
\r
376 X_UI_ScrollBox_momentum( this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options.deceleration ) : { destination: newY, duration: 0 };
\r
377 newX = momentumX.destination;
\r
378 newY = momentumY.destination;
\r
379 time = Math.max( momentumX.duration, momentumY.duration );
\r
380 this.isInTransition = 1;
\r
383 if( newX != this.x || newY != this.y ){
\r
384 // change easing function when scroller goes out of the boundaries
\r
385 if( 0 < newX || newX < this.maxScrollX || 0 < newY || newY < this.maxScrollY ){
\r
386 easing = 'quadratic';
\r
389 this.scrollTo( newX, newY, time, easing );
\r
393 this[ 'dispatch' ]( X.UI.Event.SCROLL_END );
\r
398 function X_UI_ScrollBox_resetPosition( that, time ){
\r
404 if( !this.hasHScroll || 0 < this.x ){
\r
407 if( this.x < this.maxScrollX ){
\r
408 x = this.maxScrollX;
\r
411 if( !this.hasVScroll || 0 < this.y ){
\r
414 if( this.y < this.maxScrollY ){
\r
415 y = this.maxScrollY;
\r
418 if( x === this.x && y === this.y ){
\r
419 console.log( 'no バウンド' );
\r
423 console.log( 'バウンド!' );
\r
424 this.scrollTo( x, y, time, this.options.bounceEasing );
\r
429 function X_UI_ScrollBox_translate( x, y ){
\r
430 this.containerNode.animate(
\r
445 if( this.indicators ){
\r
446 for( i = this.indicators.length; i--; ){
\r
447 this.indicators[ i ].updatePosition();
\r
452 function X_UI_ScrollBox_refresh( remove ){
\r
453 this.maxScrollX = this.boxWidth - this.containerNode.boxWidth;
\r
454 this.maxScrollY = this.boxHeight - this.containerNode.boxHeight;
\r
456 this.hasHScroll = this.User[ 'attr' ]( 'scrollX' ) && this.maxScrollX < 0;
\r
457 this.hasVScroll = this.User[ 'attr' ]( 'scrollY' ) && this.maxScrollY < 0;
\r
459 if( !this.hasHScroll ){
\r
460 this.maxScrollX = 0;
\r
461 this.scrollerWidth = this.wrapperWidth;
\r
464 if( !this.hasVScroll ){
\r
465 this.maxScrollY = 0;
\r
466 this.scrollerHeight = this.wrapperHeight;
\r
469 delete this.endTime;
\r
470 delete this.directionX;
\r
471 delete this.directionY;
\r
473 this.wrapperOffset = this.xnodeWrapper[ 'offset' ]();
\r
475 //this[ 'dispatch' ]('refresh');
\r
477 X_UI_ScrollBox_resetPosition( this, 0 );
\r
480 X.UI.ScrollBox = X.UI.ChromeBox.inherits(
\r
482 X_Class.SUPER_ACCESS,
\r
485 Constructor : function(){
\r
487 i = arguments.length,
\r
491 arg = arguments[ --i ];
\r
492 if( arg[ 'instanceOf' ] && arg[ 'instanceOf' ]( X.UI.Layout.Base ) ){
\r
495 args[ args.length ] = arg;
\r
499 this.style = DisplayNodeStyle( this,
\r
500 X_Class_newPrivate(
\r
502 X.UI.Layout.Canvas,
\r
505 layout || VerticalLayoutManager,
\r
507 name : 'ScrollBox-Scroller',
\r
515 this.style.addName( 'ScrollBox' ); */
\r
517 scrollX : function(){
\r
520 scrollY : function(){
\r
523 scrollWidth : function(){
\r
526 scrollHeight : function(){
\r
529 scrollTo : function( nodeOrX, y ){
\r