X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=0.6.x%2Fjs%2F20_ui%2F15_ScrollBox.js;h=5e49890b668810226afc62a0017fc1482b257081;hb=3d352d8bf476ab57cc333e8d02d0e6ea5efa69b7;hp=a819447a570b70c2141dc033102dcce491686fdf;hpb=9f5ab564d20a8bd6438693146ae73209c78a2c5e;p=pettanr%2FclientJs.git diff --git a/0.6.x/js/20_ui/15_ScrollBox.js b/0.6.x/js/20_ui/15_ScrollBox.js index a819447..5e49890 100644 --- a/0.6.x/js/20_ui/15_ScrollBox.js +++ b/0.6.x/js/20_ui/15_ScrollBox.js @@ -1,1036 +1,567 @@ -(function(){ -var m = Math, - ABS = new Function( 'v', 'return v<0?-v:v' ); - function Options(){}; - - X_Class_override( Options.prototype, { - hScroll : true, - vScroll : true, - x : 0, - y : 0, - bounce : true, - bounceLock : false, - momentum : true, - lockDirection : true, - useTransform : true, - useTransition : true, - topOffset : 0, - checkDOMChanges : false, // Experimental - handleClick : true, - - // Scrollbar - hScrollbar : true, - vScrollbar : true, - fixedScrollbar : X.UA.Android, - hideScrollbar : X.UA.iOS, - fadeScrollbar : X.UA.iOS, //&& has3d, - scrollbarClass : '', - - // Zoom - zoom : false, - zoomMin : 1, - zoomMax : 4, - doubleTapZoom : 2, - wheelAction : 'scroll', - - // Snap - snap : false, - snapThreshold : 1 //, - }); - - function Scrollbar( owner, dir ){ - this.owner = owner; - this.options = owner.options; - this.dir = dir; - if( dir === 'h' ){ - this.XorY = 'x'; - this.widthOrHeight = 'width'; - this.transrateXorY = 'translateX('; - }; +var X_UI_ScrollBox_SUPPORT_ATTRS = { + // スクロール開始するために必要な移動距離、縦か横、どちらか制限する場合、より重要 + directionLockThreshold : [ 10, XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.LENGTH ], + scrollXEnabled : [ true, XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.BOOLEAN ], + scrollYEnabled : [ true, XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.BOOLEAN ], + scrollEnabled : [ true, XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.BOOLEAN ], + bounceEnabled : [ true, XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.BOOLEAN ], + bounceTime : [ 300, XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.TIME ], + useWheel : [ true, XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.BOOLEAN ], + useKey : [ true, XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.BOOLEAN ], + hasScrollShadow : [ true, XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.BOOLEAN ], + scrollShadowColor : [ '#000', XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.COLOR ] }; - X_Class_override( Scrollbar.prototype, { - owner : null, - dir : null, - options : null, - XorY : 'y', - widthOrHeight : 'height', - transrateXorY : 'translateY(', - active : false, - xnodeWrapper : null, - xnodeIndicator : null, - wrapperSize : 0, - indicatorSize : 0, - maxScroll : 0, - scrollPercent : 0, + +var XUI_ScrollBox = XUI_ChromeBox.inherits( + '_ScrollBox', + X_Class.NONE, + { + layout : XUI_Layout_Canvas, + + directionLockThreshold : 10, + scrollXEnabled : true, + scrollYEnabled : true, + scrollEnabled : true, + bounceEnabled : true, + momentumEnabled : true, + bounceTime : 600, + useWheel : true, + useKey : true, + hasScrollShadow : true, + scrollShadowColor : '#000', + + scrolling : false, + + initiated : '', + moved : false, + directionLocked : '', + startTime : 0, + endTime : 0, + isInTransition : false, + + hasHScroll : false, + hasVScroll : false, + + wrapperOffset : 0, + wheelTimeout : 0, + requestFrameID : 0, + + fontSize : 0, + + scrollX : 0, // px + scrollY : 0, // px + scrollXMax : 0, // px + scrollYMax : 0, // px + scrollXRatio : 0, // この値は scroll 不要になっても保持される。 scroll 必要時に参照される + scrollYRatio : 0, + startX : 0, // px + startY : 0, // px + absStartX : 0, // px + absStartY : 0, // px + pointX : 0, // px + pointY : 0, // px + distX : 0, // px + distY : 0, // px + directionX : 0, // -1, 0, 1 + directionY : 0, // -1, 0, 1 + + lastScrollWidth : 0, + lastScrollHeight : 0, + lastBoxWidth : 0, + lastBoxHeight : 0, + + _containerNode : null, + xnodeSlider : null, + + Constructor : function( user, layout, args ){ + this[ 'Super' ]( user, layout, args ); + this._containerNode = X_Pair_get( this.containerNode ); + this.xnodeSlider = this._containerNode.xnode[ 'className' ]( 'ScrollSlider' ).listen( X_EVENT_ANIME_END, this, X_UI_ScrollBox_onAnimeEnd ); + this.xnode[ 'className' ]( 'ScrollBox' ); + }, - update : function(){ - // remove scrollbar - if( !this.active ){ - if( this.xnodeWrapper ){ - X.CSS.transform && this.xnodeIndicator.css( 'transform', '' ); - this.xnodeWrapper.css( 'display', 'none' ); + creationComplete : function(){ + XUI_Box.prototype.creationComplete.apply( this, arguments ); + }, + + calculate : function(){ + this.lastScrollWidth = this._containerNode.boxWidth; + this.lastScrollHeight = this._containerNode.boxHeight; + this.lastBoxWidth = this.boxWidth; + this.lastBoxHeight = this.boxHeight; + + XUI_Box.prototype.calculate.apply( this, arguments ); + + // TODO root の layout_complete 後に。 + // TODO calculate 前に scroll の解放。 + + if( + this.lastScrollWidth !== this._containerNode.boxWidth || + this.lastScrollHeight !== this._containerNode.boxHeight || + this.lastBoxWidth !== this.boxWidth || this.lastBoxHeight !== this.boxHeight + ){ + X_UI_rootData[ 'listenOnce' ]( XUI_Event.LAYOUT_COMPLETE, this, X_UI_ScrollBox_onLayoutComplete ); }; - return; - }; - - // create scrollbar - if( !this.xnodeWrapper ){ - // Create the scrollbar wrapper - this.xnodeWrapper = this.owner.xnodeTarget.create( 'div' ) - .className( - this.options.scrollbarClass ? - this.options.scrollbarClass + this.dir.toUpperCase() : - this.dir === 'h' ? - ( this.vScrollbar && this.vScrollbar.active ? 'ScrollBox-Scrollbar-Wrapper-V-withH' : 'ScrollBox-Scrollbar-Wrapper-H' ) : - ( this.hScrollbar && this.hScrollbar.active ? 'ScrollBox-Scrollbar-Wrapper-H-withV' : 'ScrollBox-Scrollbar-Wrapper-V' ) - ); + }, - this.options.fadeScrollbar && - this.xnodeWrapper.css( - { - opacity : 0, - transitionProperty : 'opacity', - transitionDuration : '350ms' - } - ); + scrollTo : function( x, y, opt_time, opt_easing, opt_release ){ + //if( this.scrollX === x && this.scrollY === y ) return; + + opt_time = 0 <= opt_time ? opt_time : 0; + opt_easing = opt_easing || 'circular'; + opt_release = 0 <= opt_release ? opt_release : 300; - // Create the scrollbar indicator - - this.xnodeIndicator = this.xnodeWrapper.create( 'div' ); - - !this.options.scrollbarClass && - this.xnodeIndicator.className( - this.dir === 'h' ? - 'ScrollBox-Scrollbar-Indicator-H' : - 'ScrollBox-Scrollbar-Indicator-V' - ); - //if (this.options.useTransition) bar.style.cssText += ';' + cssVendor + 'transition-timing-function:cubic-bezier(0.33,0.66,0.66,1)'; - }; + this.isInTransition = 0 < opt_time; - if( this.dir === 'h' ){ - this.wrapperSize = this.hScrollbarWrapper.clientWidth; - this.indicatorSize = m.max( m.round( wrapperSize * wrapperSize / this.owner.scrollerW ), 8 ); - //this.hScrollbarIndicator.style.width = this.hScrollbarIndicatorSize + 'px'; - this.maxScroll = wrapperSize - indicatorSize; - this.scrollPercent = maxScroll / this.owner.maxScrollX; - } else { - this.wrapperSize = this.vScrollbarWrapper.clientHeight; - this.indicatorSize = m.max( m.round( wrapperSize * wrapperSize / this.owner.scrollerH ), 8); - // this.vScrollbarIndicator.style.height = indicatorSize + 'px'; - this.maxScroll = this.vScrollbarSize - indicatorSize; - this.scrollPercent = maxScroll / this.owner.maxScrollY; - }; - this.xnodeIndicator.css( this.widthOrHeight, size + 'px' ); - // Reset position - this.updatePosition( this.owner[ this.XorY ], true ); + X_UI_ScrollBox_translate( this, x, y, opt_time, opt_easing, opt_release ); }, - updatePosition : function( pos, hidden ){ - var size; - pos = this.scrollPercent * pos; - - if( pos < 0 ){ - if( !this.options.fixedScrollbar ){ - size = this.indicatorSize + m.round( pos * 3 ); - if( size < 8 ) size = 8; - //this.xnodeIndicator.style[dir == 'h' ? 'width' : 'height'] = size + 'px'; - this.xnodeIndicator.css( this.widthOrHeight, size + 'px' ); - }; - pos = 0; - } else - if( this.maxScroll < pos ){ - if( !this.options.fixedScrollbar ){ - size = this.indicatorSize - m.round( ( pos - this.maxScroll ) * 3 ); - if( size < 8 ) size = 8; - //this.xnodeIndicator.style[dir == 'h' ? 'width' : 'height'] = size + 'px'; - this.xnodeIndicator.css( this.widthOrHeight, size + 'px' ); - pos = this.maxScroll + this.indicatorSize - size; - } else { - pos = this.maxScroll; - }; - }; - - if (this.options.useTransition){ - this.xnodeWrapper.css( { - transitionDelay : '0', - opacity : hidden && this.options.hideScrollbar ? '0' : '1' - }); - //this.xnodeIndicator.style[transform] = 'translate(' + (dir == 'h' ? pos + 'px,0)' : '0,' + pos + 'px)') + translateZ; - this.xnodeIndicator.anime( this.dir === 'h' ? { x : pos } : { y : pos } ); + _remove : function(){ + XUI_AbstractUINode.prototype._remove.apply( this, arguments ); + if( this.scrolling ){ + // remove scroll }; } - }); - - // Constructor - function iScroll( uinodeRoot, uinodeTarget, xnodeTarget, xnodeScroller, options ){ - var i; - - this.uinodeRoot = uinodeRoot; - this.uinodeTarget = uinodeTarget; - this.xnodeTarget = xnodeTarget; - this.xnodeScroller = xnodeScroller; - - // Default options - this.options = new Options(); - - // User defined options - if( options ) for (i in options) this.options[i] = options[i]; - this.options.hScroll && ( this.hScrollbar = new Scrollbar( 'h', this ) ); - this.options.vScroll && ( this.vScrollbar = new Scrollbar( 'v', this ) ); - - // Set starting position - this.x = this.options.x; - this.y = this.options.y; - - // Normalize options - this.options.useTransform = X.CSS.transform && this.options.useTransform; - this.options.hScrollbar = this.options.hScroll && this.options.hScrollbar; - this.options.vScrollbar = this.options.vScroll && this.options.vScrollbar; - this.options.zoom = this.options.useTransform && this.options.zoom; - this.options.useTransition = X.CSS.transition && this.options.useTransition; + } +); - // Helpers FIX ANDROID BUG! - // translate3d and scale doesn't work together! - // Ignoring 3d ONLY WHEN YOU SET this.options.zoom - //if ( this.options.zoom && X.UA.isAndroid ){ - // translateZ = ''; - //} - - // Set some default styles +function X_UI_ScrollBox_onLayoutBefore( e ){ + if( e[ 'cancelable' ] && this.isInTransition && X_Node_Anime_translateZ ){ + this[ 'listenOnce' ]( XUI_Event.SCROLL_END, X_UI_rootData, X_UI_rootData.calculate ); + return X_CALLBACK_PREVENT_DEFAULT; + }; + this.scrollXRatio = this.scrollX ? this.scrollXMax / this.scrollX : 0; + this.scrollYRatio = this.scrollY ? this.scrollYMax / this.scrollY : 0; + this.xnodeSlider.stop(); + this.isInTransition = false; + return X_CALLBACK_NONE; +}; - if (this.options.useTransform){ - this.scroller.style[X.CSS.transform] = 'translate(' + this.x + 'px,' + this.y + 'px)' + translateZ; - this.scroller.style[X.CSS.transformOrigin] = '0 0'; - } else { - this.scroller.style.cssText += ';position:absolute;top:' + this.y + 'px;left:' + this.x + 'px'; - }; +function X_UI_ScrollBox_onLayoutComplete( e ){ + // scroll の停止、GPU の解除 + var font = this.fontSize = this.xnodeSlider.call( 'fontSize' ); - if (this.options.useTransition){ - this.scroller.style[X.CSS.transition.Property] = this.options.useTransform ? X.CSS.cssVendor + 'transform' : 'top left'; - this.scroller.style[X.CSS.transition.Duration] = '0'; - this.scroller.style[X.CSS.transition.TimingFunction] = 'cubic-bezier(0.33,0.66,0.66,1)'; - this.options.fixedScrollbar = true; - }; + this.scrollXMax = ( this.boxWidth - this._containerNode.boxWidth ) * font | 0; + this.scrollYMax = ( this.boxHeight - this._containerNode.boxHeight ) * font | 0; - this.refresh(); + this.hasHScroll = this.scrollXEnabled && ( this.scrollXMax < -1 ); // < 0 だと + this.hasVScroll = this.scrollYEnabled && ( this.scrollYMax < -1 ); - //this._bind(RESIZE_EV, window); - X.Dom.Event.add( window, RESIZE_EV, this ); - //this._bind(START_EV); - uinodeTarget.listen( X.UI.Event.DRAG_START, this ); + if( !this.hasHScroll ){ + this.scrollXMax = 0; }; -// Prototype -X_Class_override( iScroll.prototype, { - uinodeRoot : null, - uinodeTarget : null, - xnodeTarget : null, - xnodeScroller : null, - options : null, - enabled : true, - x : 0, - y : 0, - steps : [], - scale : 1, - currPageX : 0, - currPageY : 0, - pagesX : [], - pagesY : [], - animeTimerID : 0, - wheelZoomCount : 0, - - wrapperW : 0, - wrapperH : 0, - minScrollY : 0, - scrollerW : 0, - scrollerH : 0, - maxScrollX : 0, - maxScrollY : 0, - dirX : 0, - dirY : 0, - hScrollbar : false, - vScrollbar : false, - wrapperOffsetLeft : 0, - wrapperOffsetTop : 0, - - - currPageX : 0, - currPageY : 0, - - moved : false, - animating : false, - zoomed : false, - distX : false, - distY : false, - absDistX : false, - absDistY : false, - absStartX : false, // Needed by snap threshold - absStartY : false, - startX : false, - startY : false, - pointX : false, - pointY : false, - startTime : false, - - handleEvent: function (e) { - switch(e.type) { - case X.UI.Event.DRAG : - return this._move(e); - case X.UI.Event.WHEEL : - return this._wheel(e); - case X.UI.Event.DRAG_START : - //if (!hasTouch && e.button !== 0) return; - return this._start(e); - case X.UI.Event.DRAG_END : - return this._end(e); - case X.UI.Event.ANIME_END : - return this._transitionEnd(e); - case RESIZE_EV : - return this._resize(); - } - }, - - _trigger : function( type, e ){ - - return this.uinodeTarget.dispatch( ); - }, - - _resize: function () { - X.Timer.once( X.UA.Android ? 200 : 0, this, this.refresh ); - // setTimeout( this.refresh(), isAndroid ? 200 : 0); - }, - - _updateScrollPosition: function( x, y, time ){ - if (this.zoomed) return; + if( !this.hasVScroll ){ + this.scrollYMax = 0; + }; - this.xnodeScroller.anime({ - x : this.x = this.hScrollbar && this.hScrollbar.active ? m.round(x) : 0, - y : this.y = this.vScrollbar && this.vScrollbar.active ? m.round(y) : 0 - }, time ); + delete this.endTime; + delete this.directionX; + delete this.directionY; - this.hScrollbar && this.hScrollbar.active && this.hScrollbar.updatePosition( this.x ); - this.vScrollbar && this.vScrollbar.active && this.vScrollbar.updatePosition( this.y ); - }, - - _start: function (e) { - var point = hasTouch ? e.touches[0] : e, - matrix, x, y, - ret; - //c1, c2; + X_UI_ScrollBox_resetPosition( this, 0 ); - if (!this.enabled) return; - - //if (this.options.onBeforeScrollStart) this.options.onBeforeScrollStart.call(this, e); - if( ( ret = this._trigger( X.UI.Event.SCROLL_BEFORE_START ) ) & X.Callback.PREVENT_DEFAULT ){ - return ret; + if( this.hasHScroll || this.hasVScroll ){ + // scroll が必要。 + if( this.scrolling ){ + X_UI_ScrollBox_translate( this, this.scrollXMax * this.scrollXRatio, this.scrollYMax * this.scrollYRatio, 100, '', 300 ); + } else { + // scroller 作る + this[ 'listen' ]( XUI_Event._POINTER_DOWN, this, X_UI_ScrollBox_onStart ); + X_UI_rootData[ 'listen' ]( XUI_Event.LAYOUT_BEFORE, this, X_UI_ScrollBox_onLayoutBefore ); + + X_UI_ScrollBox_translate( this, this.scrollXMax * this.scrollXRatio, this.scrollYMax * this.scrollYRatio, 100, '', 300 ); + this.scrolling = true; }; + } else + // scroll 不要 + if( this.scrolling ){ + // scroller 削除 + this[ 'unlisten' ]( XUI_Event._POINTER_DOWN, this, X_UI_ScrollBox_onStart ); + X_UI_rootData[ 'unlisten' ]( XUI_Event.LAYOUT_BEFORE, this, X_UI_ScrollBox_onLayoutBefore ); + + ( this.scrollX !== 0 || this.scrollY !== 0 ) && X_UI_ScrollBox_translate( this, 0, 0, 100, '', 300 ); + + delete this.scrolling; + delete this.scrollXRatio; + delete this.scrollYRatio; + }; +}; - //if (this.options.useTransition || this.options.zoom) this._transitionTime(0); - - this.moved = false; - this.animating = false; - this.zoomed = false; - this.distX = 0; - this.distY = 0; - this.absDistX = 0; - this.absDistY = 0; - this.dirX = 0; - this.dirY = 0; - - if (this.options.momentum) { - if (this.options.useTransform) { - // Very lame general purpose alternative to CSSMatrix - matrix = getComputedStyle(this.scroller, null)[transform].replace(/[^0-9\-.,]/g, '').split(','); - x = +(matrix[12] || matrix[4]); - y = +(matrix[13] || matrix[5]); - } else { - x = +getComputedStyle(this.scroller, null).left.replace(/[^0-9-]/g, ''); - y = +getComputedStyle(this.scroller, null).top.replace(/[^0-9-]/g, ''); - }; + // TODO use scrollLeft, scrollTop + function X_UI_ScrollBox_translate( that, x, y, opt_time, opt_easing, opt_release ){ - if (x !== this.x || y !== this.y) { - if (this.options.useTransition){ - //this._unbind(TRNEND_EV); - X.Dom.Event.remove( this.scroller, TRNEND_EV, this ); - } else { - X.Timer.cancelFrame(this.animeTimerID); + opt_time = 0 <= opt_time ? opt_time : 0; + opt_easing = opt_easing === '' ? '' : opt_easing || 'circular'; + opt_release = 0 <= opt_release ? opt_release : 300; + + that.xnodeSlider.animate( + { + x : that.scrollX, + y : that.scrollY + }, + { + x : x, + y : y + }, + opt_time, opt_easing, opt_release + ); + + that.scrollX = x; + that.scrollY = y; + + if( that.indicators ){ + for( i = that.indicators.length; i--; ){ + that.indicators[ i ].updatePosition(); }; - this.steps = this.steps ? ( this.steps.length = 0 ) : []; - this._updateScrollPosition( x, y, 0 ); - //if (this.options.onScrollEnd) this.options.onScrollEnd.call(this); - return this._trigger( X.UI.Event.SCROLL_END, e ); }; }; - this.absStartX = this.x; // Needed by snap threshold - this.absStartY = this.y; - - this.startX = this.x; - this.startY = this.y; - this.pointX = point.pageX; - this.pointY = point.pageY; - - this.startTime = e.timeStamp || X.Timer.now(); - - this.uinodeRoot.listen( X.UI.Event.DRAG, this ); - this.uinodeRoot.listen( X.UI.Event.DRAG_END, this ); +function X_UI_ScrollBox_onStart( e ){ + var ret = X_CALLBACK_NONE; - return this._trigger( X.UI.Event.SCROLL_START, e ); - }, + // React to left mouse button only + if( e.pointerType === 'mouse' && e.button !== 0 ){ + return ret; + }; - _move: function (e) { - var point = hasTouch ? e.touches[0] : e, - deltaX = point.pageX - this.pointX, - deltaY = point.pageY - this.pointY, - newX = this.x + deltaX, - newY = this.y + deltaY, - c1, c2, scale, - timestamp = e.timeStamp ||X.Timer.now(), ret; - - //if (this.options.onBeforeScrollMove) this.options.onBeforeScrollMove.call(this, e); - if( ( ret = this._trigger( X.UI.Event.SCROLL_BEFORE_MOVE ) ) & X.Callback.PREVENT_DEFAULT ){ - return ret; - }; - - this.pointX = point.pageX; - this.pointY = point.pageY; - - // Slow down if outside of the boundaries - if (newX > 0 || newX < this.maxScrollX) { - newX = this.options.bounce ? this.x + (deltaX / 2) : newX >= 0 || this.maxScrollX >= 0 ? 0 : this.maxScrollX; - }; - if (newY > this.minScrollY || newY < this.maxScrollY) { - newY = this.options.bounce ? this.y + (deltaY / 2) : newY >= this.minScrollY || this.maxScrollY >= 0 ? this.minScrollY : this.maxScrollY; - }; - - this.distX += deltaX; - this.distY += deltaY; - this.absDistX = ABS(this.distX); - this.absDistY = ABS(this.distY); - - if (this.absDistX < 6 && this.absDistY < 6) { - return; - }; - - // Lock direction - if (this.options.lockDirection) { - if (this.absDistX > this.absDistY + 5) { - newY = this.y; - deltaY = 0; - } else if (this.absDistY > this.absDistX + 5) { - newX = this.x; - deltaX = 0; - }; - }; - - this.moved = true; - this._updateScrollPosition(newX, newY, 0); - this.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; - this.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; + if( !this.scrollEnabled || ( this.initiated && e.pointerType !== this.initiated ) ){ + return ret; + }; - if (timestamp - this.startTime > 300) { - this.startTime = timestamp; - this.startX = this.x; - this.startY = this.y; - }; - - //if (this.options.onScrollMove) this.options.onScrollMove.call(this, e); - return this._trigger( X.UI.Event.SCROLL_MOVE, e ); - }, + this.initiated = e.pointerType; + this.moved = false; + this.distX = 0; + this.distY = 0; + this.directionX = 0; + this.directionY = 0; + this.directionLocked = ''; + this.startTime = X_Timer_now(); + + // スクロール中の停止 + if( this.isInTransition ){ + this.isInTransition = false; + this[ 'dispatch' ]( XUI_Event.SCROLL_END ); + // TODO current位置 + this.xnodeSlider.stop(); + }; + + this.startX = this.scrollX; + this.startY = this.scrollY; + this.absStartX = this.scrollX; + this.absStartY = this.scrollY; + this.pointX = e.pageX; + this.pointY = e.pageY; + + console.log( 'scrollstart ' + e.pageY ); + + this[ 'listen' ]( XUI_Event._POINTER_MOVE, this, X_UI_ScrollBox_onMove ); + this[ 'listen' ]( [ XUI_Event._POINTER_UP, XUI_Event._POINTER_CANCEL ], this, X_UI_ScrollBox_onEnd ); + + return ret | X_CALLBACK_PREVENT_DEFAULT; +}; + +function X_UI_ScrollBox_onMove( e ){ + var ret = X_CALLBACK_NONE, + deltaX, deltaY, timestamp, + newX, newY, + absDistX, absDistY; + // 規定以上の move でスクロール開始 + +//console.log( 'scrollmove ' + e.buttons + ' ' + e.button ); + + if( !this.scrollEnabled || e.pointerType !== this.initiated ){ + return ret; + }; - _end: function (e) { - if (hasTouch && e.touches.length !== 0) return; - - var point = hasTouch ? e.changedTouches[0] : e, - momentumX = { dist:0, time:0 }, - momentumY = { dist:0, time:0 }, - duration = ( e.timeStamp ||X.Timer.now() ) - this.startTime, - newPosX = this.x, - newPosY = this.y, - distX, distY, - newDuration, - snap, - scale; - - this.uinodeRoot.unlisten( X.UI.Event.DRAG, this ); - this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this ); - - //this._unbind(MOVE_EV, window); - //this._unbind(END_EV, window); - //this._unbind(CANCEL_EV, window); - - //if (this.options.onBeforeScrollEnd) this.options.onBeforeScrollEnd.call(this, e); - - - if (!this.moved) { - - - this._resetPos(400); - - //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e); - return; - }; - - if (duration < 300 && this.options.momentum) { - momentumX = newPosX ? this._momentum(newPosX - this.startX, duration, -this.x, this.scrollerW - this.wrapperW + this.x, this.options.bounce ? this.wrapperW : 0) : momentumX; - momentumY = newPosY ? this._momentum(newPosY - this.startY, duration, -this.y, (this.maxScrollY < 0 ? this.scrollerH - this.wrapperH + this.y - this.minScrollY : 0), this.options.bounce ? this.wrapperH : 0) : momentumY; - - newPosX = this.x + momentumX.dist; - newPosY = this.y + momentumY.dist; + if( e.buttons !== 1 ){ + return X_UI_ScrollBox_onEnd.call( this, e ); + }; - if ((this.x > 0 && newPosX > 0) || (this.x < this.maxScrollX && newPosX < this.maxScrollX)) momentumX = { dist:0, time:0 }; - if ((this.y > this.minScrollY && newPosY > this.minScrollY) || (this.y < this.maxScrollY && newPosY < this.maxScrollY)) momentumY = { dist:0, time:0 }; - - if (momentumX.dist || momentumY.dist) { - newDuration = m.max(m.max(momentumX.time, momentumY.time), 10); - - this.scrollTo(m.round(newPosX), m.round(newPosY), newDuration); - - //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e); - return; - }; - }; + // gpu の用意 + if( !this.xnodeSlider[ '_anime' ] ){ + //console.log( 'gpuレイヤーの用意 ' + e.pageY ); + //console.log( 'mov1 x:' + this.scrollX + ' y:' + this.scrollY ); + X_UI_ScrollBox_translate( this, this.scrollX, this.scrollY, 0, '', 300 ); + return ret; + }; - this._resetPos(200); - //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e); - }, - - /* - _onZoomEndEvent : null, - _onZoomEndTimerComplete : function(){ - this.options.onZoomEnd.call( this, this._onZoomEndEvent ); - }, - */ - - /* - _onDobleTapTimerPoint : null, - _onDobleTapTimerComplete : function () { - var point = this._onDobleTapTimerPoint, - target, ev; - this.doubleTapTimer = null; + deltaX = e.pageX - this.pointX; + deltaY = e.pageY - this.pointY; + timestamp = X_Timer_now(); - // Find the last touched element - target = point.target; - while (target.nodeType !== 1) target = target.parentNode; + this.pointX = e.pageX; + this.pointY = e.pageY; - if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') { - ev = document.createEvent('MouseEvents'); - ev.initMouseEvent('click', true, true, e.view, 1, - point.screenX, point.screenY, point.clientX, point.clientY, - e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, - 0, null); - ev._fake = true; - target.dispatchEvent(ev); - } - },*/ + this.distX += deltaX; + this.distY += deltaY; + absDistX = Math.abs(this.distX); + absDistY = Math.abs(this.distY); - _resetPos: function (time) { - var resetX = - 0 <= this.x ? - 0 : - this.x < this.maxScrollX ? - this.maxScrollX : - this.x, - resetY = - this.minScrollY <= this.y || 0 < this.maxScrollY ? - this.minScrollY : - this.y < this.maxScrollY ? - this.maxScrollY : - this.y; + // We need to move at least 10 pixels for the scrolling to initiate + if( 300 < timestamp - this.endTime && ( absDistX < 10 && absDistY < 10 ) ){ + return ret; + }; - if( resetX === this.x && resetY === this.y ){ - if( this.moved ){ - this.moved = false; - this._trigger( X.UI.Event.SCROLL_END ); - //if (this.options.onScrollEnd) this.options.onScrollEnd.call(this); // Execute custom code on scroll end - }; - if( this.options.hideScrollbar ){ - this.hScrollbar && this.hScrollbar.active && - - //if (vendor == 'webkit') this.hScrollbarWrapper.style[transitionDelay] = '300ms'; - //this.hScrollbarWrapper.style.opacity = '0'; - this.hScrollbar.xnodeWraper.anime( { - opacity : 0 - }, 300 ); - this.vScrollbar && this.vScrollbar.active && - //if (vendor == 'webkit') this.vScrollbarWrapper.style[transitionDelay] = '300ms'; - //this.vScrollbarWrapper.style.opacity = '0'; - this.hScrollbar.xnodeWraper.anime( { - opacity : 0 - }, 300 ); - }; - return; - }; - this.scrollTo(resetX, resetY, time || 0); - }, - - _wheel: function (e) { - var wheelDeltaX, wheelDeltaY, - deltaX, deltaY, - deltaScale; -/* - if ('wheelDeltaX' in e) { - wheelDeltaX = e.wheelDeltaX / 12; - wheelDeltaY = e.wheelDeltaY / 12; - } else if('wheelDelta' in e) { - wheelDeltaX = wheelDeltaY = e.wheelDelta / 12; - } else if ('detail' in e) { - wheelDeltaX = wheelDeltaY = -e.detail * 3; + // If you are scrolling in one direction lock the other + if( !this.directionLocked ){ + if( absDistX > absDistY + this.directionLockThreshold ){ + this.directionLocked = 'h'; // lock horizontally + } else + if( absDistY >= absDistX + this.directionLockThreshold ){ + this.directionLocked = 'v'; // lock vertically } else { - return; - } */ - - deltaX = this.x + e.wheelDeltaX; - deltaY = this.y + e.wheelDeltaY; - - deltaX = - 0 < deltaX ? - 0 : - deltaX < this.maxScrollX ? - this.maxScrollX : deltaX; - - deltaY = - this.minScrollY < deltaY ? - this.minScrollY : - deltaY < this.maxScrollY ? - this.maxScrollY : deltaY; - - this.maxScrollY < 0 && this.scrollTo(deltaX, deltaY, 0); - }, - - /* - _wheelTimerCompleteEvent : null, - _wheelTimerComplete : function() { - this.wheelZoomCount--; - if (!this.wheelZoomCount && this.options.onZoomEnd) this.options.onZoomEnd.call(this, this._wheelTimerCompleteEvent ); - }, - */ - - _transitionEnd: function( e ){ - if( e.target !== this.xnodeScroller ) return; - - //this._unbind(TRNEND_EV); - //X.Dom.Event.remove( this.scroller, TRNEND_EV, this ); - this.animating = false; - - this._startAnime(); - - return X.Callback.UN_LISTEN; - }, - - - /** - * - * Utilities - * - */ - _startAnime: function () { - var startX = this.x, - startY = this.y, - step, animate; - - if (this.animating) return; - - if (!this.steps.length) { - this._resetPos(400); - return; + this.directionLocked = 'n'; // no lock }; - - step = this.steps.shift(); - - if( step.x === startX && step.y === startY ) step.time = 0; - - this.animating = true; - this.moved = true; - - //if (this.options.useTransition) { - //this._transitionTime(step.time); - this._updateScrollPosition( step.x, step.y, step.time ); - //this.animating = false; - this.xnodeScroller.listenOnce( X.UI.Event.ANIME_END, this ); - //step.time ? X.Dom.Event.add( this.scroller, TRNEND_EV, this ) /* this._bind(TRNEND_EV) */ : this._resetPos(0); - //return; - //} - //this._doAnimate( X.Timer.now(), step, startX, startY ); - }, - -/* - _doAnimate : function( startTime, step, startX, startY ){ - var now =X.Timer.now(), - easeOut, newX, newY; - - if (now >= startTime + step.time) { - this._updateScrollPosition(step.x, step.y); - this.animating = false; - //if (this.options.onAnimationEnd) this.options.onAnimationEnd.call( this ); // Execute custom code on animation end - this._startAnime(); - return; - }; - - now = (now - startTime) / step.time - 1; - easeOut = m.sqrt(1 - now * now); - newX = (step.x - startX) * easeOut + startX; - newY = (step.y - startY) * easeOut + startY; - this._updateScrollPosition(newX, newY); - if( this.animating ) this.animeTimerID = X.Timer.requestFrame( this, this._doAnimate, [ startTime, step, startX, startY ] ); - }, -*/ - - _momentum: function (dist, time, maxDistUpper, maxDistLower, size) { - var deceleration = 0.0006, - speed = ABS(dist) / time, - newDist = (speed * speed) / (2 * deceleration), - newTime = 0, outsideDist = 0; - - // Proportinally reduce speed if we are outside of the boundaries - if (dist > 0 && newDist > maxDistUpper) { - outsideDist = size / (6 / (newDist / speed * deceleration)); - maxDistUpper = maxDistUpper + outsideDist; - speed = speed * maxDistUpper / newDist; - newDist = maxDistUpper; - } else if (dist < 0 && newDist > maxDistLower) { - outsideDist = size / (6 / (newDist / speed * deceleration)); - maxDistLower = maxDistLower + outsideDist; - speed = speed * maxDistLower / newDist; - newDist = maxDistLower; - } - - newDist = newDist * (dist < 0 ? -1 : 1); - newTime = speed / deceleration; - - return { dist: newDist, time: m.round(newTime) }; - }, + }; - _offset: function (el) { - var left = -el.offsetLeft, - top = -el.offsetTop; - - while (el = el.offsetParent) { - left -= el.offsetLeft; - top -= el.offsetTop; - } - - if (el != this.wrapper) { - left *= this.scale; - top *= this.scale; - } + if( this.directionLocked === 'h' ){ + deltaY = 0; + } else + if( this.directionLocked === 'v' ){ + deltaX = 0; + }; - return { left: left, top: top }; - }, + deltaX = this.hasHScroll ? deltaX : 0; + deltaY = this.hasVScroll ? deltaY : 0; - /* - _bind: function (type, el, bubble) { - X.Dom.Event.add( el || this.scroller, type, this ); - }, + if( !this.moved ){ + this[ 'dispatch' ]( XUI_Event.SCROLL_BEFORE_MOVE ); + this.moved = true; + this.minusX = deltaX; + this.minusY = deltaY; + } else { + this[ 'dispatch' ]( XUI_Event.SCROLL_MOVE ); + }; - _unbind: function (type, el, bubble) { - X.Dom.Event.remove( el || this.scroller, type, this ); - }, - */ + newX = this.scrollX + deltaX;// - this.minusX; + newY = this.scrollY + deltaY;// - this.minusY; - /** - * - * Public methods - * - */ - destroy: function () { - this.scroller.style[transform] = ''; + // Slow down if outside of the boundaries + if( 0 < newX || newX < this.scrollXMax ){ + newX = this.bounceEnabled ? this.scrollX + ( deltaX ) / 3 : 0 < newX ? 0 : this.scrollXMax; + }; + + if( 0 < newY || newY < this.scrollYMax ){ + //console.log( 'slow... ' + newY + ' ' + this.scrollYMax ); + newY = this.bounceEnabled ? this.scrollY + ( deltaY ) / 3 : 0 < newY ? 0 : this.scrollYMax; + }; - // Remove the scrollbars - this.hScrollbar && this.hScrollbar.destroy(); - this.vScrollbar && this.vScrollbar.destroy(); + this.directionX = 0 < deltaX ? -1 : deltaX < 0 ? 1 : 0; + this.directionY = 0 < deltaY ? -1 : deltaY < 0 ? 1 : 0; - // Remove the event listeners - X.Dom.Event.add( window, RESIZE_EV, this ); - this.uinodeTarget.unlisten( X.UI.Event.DRAG_START, this ); - this.uinodeRoot.unlisten( X.UI.Event.DRAG, this ); - this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this ); - - //this._unbind(RESIZE_EV, window); - //this._unbind(START_EV); - //this._unbind(MOVE_EV, window); - //this._unbind(END_EV, window); - //this._unbind(CANCEL_EV, window); - - if (!this.options.hasTouch) { - //this._unbind('wheel'); - } - - // if (this.options.useTransition) this._unbind(TRNEND_EV); - this.options.useTransition && X.Dom.Event.remove( this.scroller, TRNEND_EV, this ); - - //if (this.options.checkDOMChanges) clearInterval(this.checkDOMTime); - - //if (this.options.onDestroy) this.options.onDestroy.call(this); - }, + //console.log( 'mov2 x:' + newX + ' y:' + newY ); + X_UI_ScrollBox_translate( this, newX, newY, 0, '', 300 ); - refresh: function () { - var offset, - i, l, - els, - pos = 0, - page = 0; + if( 300 < timestamp - this.startTime ){ + this.startTime = timestamp; + this.startX = this.scrollX; + this.startY = this.scrollY; + }; + // イベントの拘束 + return ret | X_CALLBACK_PREVENT_DEFAULT | X_CALLBACK_CAPTURE_POINTER; +}; + +function X_UI_ScrollBox_onEnd( e ){ + var ret = X_CALLBACK_NONE, + time = 0, + easing = '', + newX, newY, + momentumX, momentumY, + duration, distanceX, distanceY; + + this[ 'unlisten' ]( XUI_Event._POINTER_MOVE, this, X_UI_ScrollBox_onMove ); + this[ 'unlisten' ]( [ XUI_Event._POINTER_UP, XUI_Event._POINTER_CANCEL ], this, X_UI_ScrollBox_onEnd ); + + if( !this.scrollEnabled || e.pointerType !== this.initiated ){ + return ret; + }; - if (this.scale < this.options.zoomMin) this.scale = this.options.zoomMin; - this.wrapperW = this.wrapper.clientWidth || 1; - this.wrapperH = this.wrapper.clientHeight || 1; + delete this.isInTransition; + delete this.initiated; + this.endTime = X_Timer_now(); - this.minScrollY = -this.options.topOffset || 0; - this.scrollerW = m.round(this.scroller.offsetWidth * this.scale); - this.scrollerH = m.round((this.scroller.offsetHeight + this.minScrollY) * this.scale); - this.maxScrollX = this.wrapperW - this.scrollerW; - this.maxScrollY = this.wrapperH - this.scrollerH + this.minScrollY; - this.dirX = 0; - this.dirY = 0; + duration = this.endTime - this.startTime; + newX = Math.round( this.scrollX ); + newY = Math.round( this.scrollY ); + distanceX = Math.abs(newX - this.startX); + distanceY = Math.abs(newY - this.startY); - // if (this.options.onRefresh) this.options.onRefresh.call(this); - this._trigger( X.UI.Event.SCROLL_REFRESH, {} ); + // reset if we are outside of the boundaries + if( X_UI_ScrollBox_resetPosition( this, this.bounceTime ) ){ + return ret; + }; - this.hScrollbar && ( this.hScrollbar.active = this.maxScrollX < 0 ); - this.vScrollbar && ( this.vScrollbar.active = !this.options.bounceLock && !this.hScroll || this.scrollerH > this.wrapperH ); - - offset = this._offset(this.wrapper); - this.wrapperOffsetLeft = -offset.left; - this.wrapperOffsetTop = -offset.top; + // we scrolled less than 10 pixels + if( !this.moved ){ + // this[ 'dispatch' ]( X_EVENT_CANCELED ); + console.log( 'we scrolled less than 10 pixels ' + e.pageY ); + return ret; + }; - // Prepare snap - if (typeof this.options.snap == 'string') { - this.pagesX = []; - this.pagesY = []; - els = this.scroller.querySelectorAll(this.options.snap); - for (i=0, l=els.length; i= this.maxScrollX) { - this.pagesX[page] = pos; - pos = pos - this.wrapperW; - page++; - } - if (this.maxScrollX%this.wrapperW) this.pagesX[this.pagesX.length] = this.maxScrollX - this.pagesX[this.pagesX.length-1] + this.pagesX[this.pagesX.length-1]; + // start momentum animation if needed + if( this.momentumEnabled && duration < 300 ){ + momentumX = this.hasHScroll ? + X_UI_ScrollBox_momentum( this.scrollX, this.startX, duration, this.scrollXMax, this.bounceEnabled ? this.boxWidth * this.fontSize : 0, this.deceleration ) : + { destination: newX, duration: 0 }; + momentumY = this.hasVScroll ? + X_UI_ScrollBox_momentum( this.scrollY, this.startY, duration, this.scrollYMax, this.bounceEnabled ? this.boxHeight * this.fontSize : 0, this.deceleration ) : + { destination: newY, duration: 0 }; + newX = momentumX.destination; + newY = momentumY.destination; + time = Math.max( momentumX.duration, momentumY.duration ) | 0; + this.isInTransition = true; + } else { + console.log( '慣性無し' ); + }; - pos = 0; - page = 0; - this.pagesY = []; - while (pos >= this.maxScrollY) { - this.pagesY[page] = pos; - pos = pos - this.wrapperH; - page++; - } - if (this.maxScrollY%this.wrapperH) this.pagesY[this.pagesY.length] = this.maxScrollY - this.pagesY[this.pagesY.length-1] + this.pagesY[this.pagesY.length-1]; - } + if( newX != this.scrollX || newY != this.scrollY ){ + // change easing function when scroller goes out of the boundaries + if( 0 < newX || newX < this.scrollXMax || 0 < newY || newY < this.scrollYMax ){ + easing = 'quadratic'; + }; - // Prepare the scrollbars - this._scrollbar('h'); - this._scrollbar('v'); + console.log( 'end2 x:' + newX + ' y:' + newY + ' t:' + time ); + this.scrollTo( newX, newY, time, easing, 1000 ); + return ret; + }; - if (!this.zoomed) { - this.scroller.style[transitionDuration] = '0'; - this._resetPos(400); - } - }, + console.log( 'end1 x:' + newX + ' y:' + newY ); + this.scrollTo( newX, newY, 0, '', 1000 ); // ensures that the last position is rounded - scrollTo: function (x, y, time, relative) { - var step = x, - i, l; + this[ 'dispatch' ]( XUI_Event.SCROLL_END ); + + return ret; +}; - this.stop(); +function X_UI_ScrollBox_resetPosition( that, time ){ + var x = that.scrollX, + y = that.scrollY; - if( !step.length ) step = [{ x: x, y: y, time: time, relative: relative }]; - - for( i = 0, l = step.length; i < l; ++i ){ - if( step[ i ].relative ){ - step[ i ].x = this.x - step[ i ].x; - step[ i ].y = this.y - step[ i ].y; - }; - this.steps.push( { - x : step[i].x, - y : step[i].y, - time : step[i].time || 0 - }); - }; + time = time || 0; - this._startAnime(); - }, + if( !that.hasHScroll || 0 < that.scrollX ){ + x = 0; + } else + if( that.scrollX < that.scrollXMax ){ + x = that.scrollXMax; + }; - disable: function () { - this.stop(); - this._resetPos(0); - this.enabled = false; + if( !that.hasVScroll || 0 < that.scrollY ){ + y = 0; + } else + if( that.scrollY < that.scrollYMax ){ + y = that.scrollYMax; + }; - // If disabled after touchstart we make sure that there are no left over events - //this._unbind(MOVE_EV, window); - //this._unbind(END_EV, window); - //this._unbind(CANCEL_EV, window); - this.uinodeRoot.unlisten( X.UI.Event.DRAG, this ); - this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this ); - }, - - enable: function () { - this.enabled = true; - }, - - stop: function () { - //if (this.options.useTransition) this._unbind(TRNEND_EV); - //else X.Timer.cancelFrame( this.animeTimerID ); - /* - if (this.options.useTransition){ - X.Dom.Event.remove( this.scroller, TRNEND_EV, this ); - } else { - X.Timer.cancelFrame(this.animeTimerID); - }; - */ - this.xnodeScroller.stop(); - if( this.steps ) this.steps.length = 0; - this.moved = false; - this.animating = false; - }, - - isReady: function () { - return !this.moved && !this.zoomed && !this.animating; - } -} ); + if( x === that.scrollX && y === that.scrollY ){ + console.log( 'no バウンド y:' + y + ' max:' + that.scrollYMax ); + return false; + }; + console.log( ' ===> resetPosition - バウンド!' ); + console.log( ' x:' + x + ' y:' + y ); + that.scrollTo( x, y, time, that.bounceEasing, 1000 ); + return true; +}; -X.UI._ScrollBox = X.UI._ChromeBox.inherits( - '_ScrollBox', - X.Class.PRIVATE_DATA | X.Class.SUPER_ACCESS, - { - //elmScroll : null, - //elmScroller : null, - //elmScrollbar : null, - - scrolling : false, - _scrollX : 0, - _scrollY : 0, - scrollXPercent : 0, - scrollYPercent : 0, - - _containerNode : null, - scrollManager : null, - - Constructor : function( layout, args ){ - this.SuperConstructor( layout, args ); - this._containerNode = _X_Class_getPrivate( this.containerNode ); - }, - - creationComplete : function(){ - X.UI._AbstractUINode.prototype.creationComplete.call( this, arguments ); - this.scrollManager = new iScroll( this.root, this.User, this.rawElement, this._containerNode.rawElement ); - this._check(); - }, - - calculate : function(){ - X.UI._AbstractUINode.prototype.calculate.call( this, arguments ); - this._check(); - }, - - _check : function(){ - if( this.w < this._containerNode.w || this.h < this._containerNode.h ){ - // scroll - if( this.scrolling ){ - // fix scroll position from scrollXPercent, scrollYPercent - - } else { - // create scroller - this.listen( X.UI.Event.POINTER_START, this ); - - - this._move( 0, 0 ); - - this.scrolling = true; - }; - } else - // no scroll - if( this.scrolling ){ - // remove scroller - - ( this._scrollX !== 0 || this._scrollY !== 0 ) && this._move( 0, 0 ); - - delete this.scrolling; - }; - }, - - handleEvent : function( e ){ - switch( e.type ){ - case X.UI.Event.POINTER_START : - this.listen( X.UI.Event.POINTER_MOVE, this ); - this.listen( X.UI.Event.POINTER_END, this ); - - break; - case X.UI.Event.POINTER_MOVE : - - break; - case X.UI.Event.POINTER_END : - this.unlisten( X.UI.Event.POINTER_MOVE, this ); - this.unlisten( X.UI.Event.POINTER_END, this ); - - break; - }; - }, - - _move : function( x, y ){ - - }, - - _remove : function(){ - X.UI._AbstractUINode.prototype._remove.call( this, arguments ); - if( this.scrolling ){ - // remove scroll - }; - } - - } -); +function X_UI_ScrollBox_onAnimeEnd( e ){ + if( e.target !== this.xnodeSlider || !this.isInTransition ){ + return X_CALLBACK_NONE; + }; + if( !X_UI_ScrollBox_resetPosition( this, this.bounceTime ) ){ + this.isInTransition = false; + this[ 'dispatch' ]( XUI_Event.SCROLL_END ); + }; + return X_CALLBACK_NONE; +}; + +function X_UI_ScrollBox_momentum( current, start, time, lowerMargin, wrapperSize, deceleration ){ + var distance = current - start, + speed = Math.abs( distance ) / time, + destination, + duration; + + deceleration = deceleration === undefined ? 0.0006 : deceleration; + + destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 ); + duration = speed / deceleration; + + if( destination < lowerMargin ){ + destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin; + distance = Math.abs( destination - current ); + duration = distance / speed; + } else + if ( destination > 0 ) { + destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0; + distance = Math.abs( current ) + destination; + duration = distance / speed; + }; -})(); + return { + destination : Math.round( destination ), + duration : duration + }; +}; X.UI.ScrollBox = X.UI.ChromeBox.inherits( 'ScrollBox', - X.Class.SUPER_ACCESS, - X.UI._ScrollBox, + X_Class.NONE, { Constructor : function(){ - var args = [], + var supports, slider; + + if( XUI_ScrollBox.prototype.usableAttrs === XUI_ChromeBox.prototype.usableAttrs ){ + XUI_ScrollBox.prototype.usableAttrs = supports = XUI_Attr_createAttrDef( XUI_Attr_Support, X_UI_ScrollBox_SUPPORT_ATTRS ); + + XUI_ScrollBox.prototype.attrClass = XUI_Attr_preset( XUI_Box.prototype.attrClass, supports, { width : '100%', height : '100%', bgColor : 0x111111 } ); + }; + + var args = [ + XUI_Layout_Vertical, + { + name : 'ScrollBox-Scroller', + role : 'container', + width : 'auto', + minWidth : '100%', + height : 'auto', + minHeight : '100%' + } + ], i = arguments.length, - arg, layout; + arg, attr; for( ; i; ){ arg = arguments[ --i ]; - if( arg.instanceOf && arg.instanceOf( X.UI.Layout.Base ) ){ - layout = arg; - } else { + if( arg[ 'instanceOf' ] && arg[ 'instanceOf' ]( XUI_LayoutBase ) ){ + args[ 0 ] = arg; + } else + if( arg[ 'instanceOf' ] && arg[ 'instanceOf' ]( X.UI.AbstractUINode ) ){ args[ args.length ] = arg; + } else + if( X_Type_isObject( arg ) ){ + args[ args.length ] = attr = arg; + slider = attr.scrollSlider; }; }; - /* - this.style = DisplayNodeStyle( this, - X_Class_newPrivate( + + X_Pair_create( + this, + XUI_ScrollBox( this, - X.UI.Layout.Canvas, + null, [ - Box( - layout || VerticalLayoutManager, - { - name : 'ScrollBox-Scroller', - role : 'container' - }, - args - ) + slider || X.UI.VBox.apply( 0, args ) ] ) ); - this.style.addName( 'ScrollBox' ); */ + + //attr && this.attr( attr ); }, scrollX : function(){ @@ -1048,4 +579,5 @@ X.UI.ScrollBox = X.UI.ChromeBox.inherits( } } -); \ No newline at end of file +); +