Engine.RecycledScrollItem = class
{
	constructor(data /*TData*/, width /*int*/, height /*int*/, getDrawableFunc /*DrawableSource*/, returnDrawableFunc /*ReturnDrawable*/)
	{
		this.height = height;
		this.width = width;
		this.getDrawableFunc = getDrawableFunc;
		this.returnDrawableFunc = returnDrawableFunc;
		this.data = data;
		this._y;
		this._x;
		this.currentDrawable = null;
	}

	getDrawable()
	{
		if (this.currentDrawable != null)
		{
			return this.currentDrawable;
		}
		else
		{
			this.currentDrawable = this.getDrawableFunc(this.data);
			return this.currentDrawable;
		}
	}

	get y() { return this._y; }
	set y(value)
	{
		this._y = value;
		if (this.currentDrawable != null) this.currentDrawable.y = this._y;
	}

	get x() { return this._x; }
	set x(value)
	{
		this._x = value;
		if (this.currentDrawable != null) this.currentDrawable.x = this._x;
	}

	get hasClip() { return this.currentDrawable != null; }

	get clip() { return this.currentDrawable; }

	addTo(parentScrollPane /*Drawable*/)
	{
		this.parentScrollPane = parentScrollPane;
		this.currentDrawable = this.getDrawable();
		if (this.currentDrawable != null)
		{
			this.currentDrawable.y = this.y;
			this.currentDrawable.x = this.x;
			this.parentScrollPane.addChild(this.currentDrawable);
		}
	}

	remove()
	{
		if (this.currentDrawable != null)
		{
			this.currentDrawable.removeFromParent();
			this.returnDrawableFunc(this.currentDrawable);
			this.currentDrawable = null;
		}
	}

	setData(data /*TData*/)
	{
		this.data = data;
	}
}


Engine.ScrollPaneVHRecycling = class extends Engine.Drawable
{
	get items()
	{
		return this.scrollItems;
	}

	getScrollHeight()
	{
		if (this.scrollItems.length == 0) return 0;
		
		var lastItem = this.scrollItems[this.scrollItems.length - 1];
		
		if (this.horz) return Math.max(0, Math.trunc(lastItem.x + lastItem.width - this.visibleHeight));
		return Math.max(0, Math.trunc(lastItem.y + lastItem.height - this.visibleHeight));
	}

    throwOnScroll()
    {
        this.onScroll.call(this);
    }

	setScrollPercentFromScrollAmount()
	{
		this.scrollPercent = this.currentScrollAmount / this.getScrollHeight();
	}

	constructor(visibleHeight /*int*/, numColumns /*int*/, horz /*bool*/)
	{
		super();

		if (visibleHeight === undefined) visibleHeight = 0;
		if (numColumns === undefined) numColumns = 1;
		if (horz === undefined) horz = false; 

		this.numColumns = 1;
		this.itemClip = new Engine.Drawable();
		this.scrollItems = [];
		this.scrollPositionsByIndex = [];
		this.currentScrollAmount = 0;
		this.destinationScrollAmount = 0;
		this.horzPadding = 0;
		this.vertPadding = 0;
		this.isScrollToPercent = false;
		this.scrollAmount = 0;
		this.needsUpdate = false;
		this.baseDecelleration = 800;
		this.decelleration = 800;
		this.max_velocity = 2000;
		this.vel = 0;
		this.velocityScrolledPastEnd = false;
		
		this.horz = horz;
		this.numColumns = numColumns;
		this.visibleHeight = visibleHeight;
		this.scrollAreaHeight = visibleHeight;
		this.scrollPercent = 0;

		this.onScroll = new Action();

		this.addChild(this.itemClip);

		Engine.EnterFrameManagerInstance.add(this, this.update, true);
	}

	init(visibleHeight /*int*/, numColumns /*int*/, horz /*bool*/)
	{
		if (visibleHeight === undefined) visibleHeight = 0;
		if (numColumns === undefined) numColumns = 1;
		if (horz === undefined) horz = false;

		this.horz = horz;
		this.numColumns = numColumns;
		this.visibleHeight = visibleHeight;
		this.scrollAreaHeight = visibleHeight;
		this.updateVisible();
	}

	clearPane()
	{
		for (var i = this.scrollItems.length - 1; i >= 0; i--)
		{
			this.scrollItems[i].remove();
		}
		this.scrollItems.length = 0;// = [];
	}

	calculateNextAvailablePosition(force /*bool*/)
	{
		if (this.scrollPositionsByIndex.length + 1 == this.scrollItems.length && force == false)
		{
			return this.scrollPositionsByIndex[this.scrollPositionsByIndex.length - 1];
		}
		
		this.scrollPositionsByIndex = [];
		var height = 0;
		var xPos = 0;
		this.scrollPositionsByIndex.push(new PIXI.Point(0, 0));
		
		if (this.horz)
		{
			for (var i = 0; i < this.scrollItems.length; i++)
			{
				height += Math.trunc(this.scrollItems[i].height) + this.vertPadding;
				
				if ((i + 1) % this.numColumns == 0)
				{
					xPos += Math.trunc(this.scrollItems[i].width) + this.horzPadding;
					height = 0;
				}
				
				this.scrollPositionsByIndex.push(new PIXI.Point(xPos, height));
			}
		}
		else
		{
			for (var i = 0; i < this.scrollItems.length; i++)
			{
				xPos += Math.trunc(this.scrollItems[i].width) + this.horzPadding;
				if ((i + 1) % this.numColumns == 0)
				{
					height += Math.trunc(this.scrollItems[i].height) + this.vertPadding;
					xPos = 0;
				}
				this.scrollPositionsByIndex.push(new PIXI.Point(xPos, height));
			}
		}
		
		return this.scrollPositionsByIndex[this.scrollPositionsByIndex.length - 1];
	}

	addItem(item /*RecycledScrollItem<TData,TClip>*/)
	{
		var itemPos = this.calculateNextAvailablePosition();
		item.y = itemPos.y;
		item.x = itemPos.x;
		
		this.scrollItems.push(item);
		this.updateVisible();
	}

	scrollUp()
	{
		this.addScrollVelocity(-100);
	}

	scrollDown()
	{
		this.addScrollVelocity(100);
	}

	scrollLeft()
	{
		this.addScrollVelocity(-500);
	}

	scrollRight()
	{
		this.addScrollVelocity(500);
	}

	updateItemPositions(horzPadding /*int*/, vertPadding /*int*/, headers /*HashSet<int>*/)
	{
		if (horzPadding === undefined) horzPadding = 0;
		if (vertPadding === undefined) vertPadding = 0;
		if (headers === undefined) headers = null;

		this.horzPadding = horzPadding;
		this.vertPadding = vertPadding;

		var height = 0;
		var xPos = 0;
		
		if (this.horz)
		{
			for (var i = 0; i < this.scrollItems.length; i++)
			{
				this.scrollItems[i].y = height;
				this.scrollItems[i].x = xPos;
				
				height += Math.trunc(this.scrollItems[i].height) + vertPadding;
				if ((i + 1) % this.numColumns == 0 || this.numColumns == 0)
				{
					xPos += Math.trunc(this.scrollItems[i].width) + horzPadding;
					height = 0;
				}
			}
		}
		else
		{
			var rowCount = 0;
			for (i = 0; i < this.scrollItems.length; i++)
			{
				rowCount++;
				
				var isHeader = (headers != null) && (headers.indexOf(i) != -1);
				if (isHeader)
				{
					if (xPos != 0) //This can only happen when an item came before us, so i-1 a safe assumption i think
					{
						if (i > 0) height += Math.trunc(this.scrollItems[i-1].height) + vertPadding;
						else height += Math.trunc(this.scrollItems[i - 1].height) * 2 + vertPadding;
					}
					xPos = 0;
				}
				
				this.scrollItems[i].y = height;
				this.scrollItems[i].x = xPos;
				
				xPos += Math.trunc(this.scrollItems[i].width) + horzPadding;
				if (rowCount % this.numColumns == 0 || this.numColumns == 0 || isHeader)
				{
					height += Math.trunc(this.scrollItems[i].height) + vertPadding;
					xPos = 0;
					rowCount = 0;
				}
			}
		}
		this.updateVisible();
		this.needsUpdate = true;
	}

	updateVisible()
	{
		for (var i = 0; i < this.scrollItems.length; i++)
		{
			var itemHeight = Math.trunc(this.scrollItems[i].height);
			var itemTop = Math.trunc(this.scrollItems[i].y);
			var itemBottom = itemTop + itemHeight;
			var visiblePadding = 0;

			var bottomCutoff = Math.trunc(this.currentScrollAmount + this.visibleHeight + visiblePadding);
			var topCutoff = Math.trunc(this.currentScrollAmount - visiblePadding);
			
			var itemWidth = Math.trunc(this.scrollItems[i].width);
			var itemLeft = Math.trunc(this.scrollItems[i].x);
			var itemRight = itemLeft + itemWidth;
			
			if (this.horz && (itemRight > topCutoff && itemLeft < bottomCutoff))
			{
				if (this.scrollItems[i].getDrawable().parent != this.itemClip) this.scrollItems[i].addTo(this.itemClip);
			}
			else if (!this.horz && (itemBottom > topCutoff && itemTop < bottomCutoff))
			{
				if (this.scrollItems[i].getDrawable().parent != this.itemClip) this.scrollItems[i].addTo(this.itemClip);
			}
			else
			{
				this.scrollItems[i].remove();
			}
		}
	}

	scrollDownOneItem()
	{
		if (this.items.length == 0) return;

		var visibleItems = this.numColumns * Math.ceil(this.visibleHeight / (this.horz ? this.items[0].width : this.items[0].height));
		if (this.items.length == visibleItems) this.scrollToPercent(1);
		else this.scrollToPercent(this.scrollPercent + (1 / (this.items.length - visibleItems)));
	}

	scrollUpOneItem()
	{
		if (this.items.length == 0) return;

		var visibleItems = this.numColumns * Math.ceil(this.visibleHeight / (this.horz ? this.items[0].width : this.items[0].height));
		if (this.items.length == visibleItems) this.scrollToPercent(0);
		else this.scrollToPercent(this.scrollPercent - (1 / (this.items.length - visibleItems)));
	}

	scrollDownOneColumn()
    {
        if (this.items.length == 0) return;
        var scrollAmount = this.horz ? this.items[0].width + this.horzPadding : this.items[0].height + this.vertPadding;
        var scrollHeight = this.getScrollHeight();
        var newPercent = 0;
        if (scrollHeight > 0) { newPercent = this.scrollPercent + scrollAmount / scrollHeight; }
    	this.scrollToPercent(newPercent);
    }

    scrollUpOneColumn()
    {
        if (this.items.length == 0) return;
        var scrollAmount = this.horz ? this.items[0].width + this.horzPadding : this.items[0].height + this.vertPadding;
        var scrollHeight = this.getScrollHeight();
        var newPercent = 0;
        if (scrollHeight > 0) { newPercent = this.scrollPercent - scrollAmount / scrollHeight; }
        this.scrollToPercent(newPercent);
    }

    scrollToIndex(index, instant)
    {
    	if (instant === undefined) instant = false;

        index = Math.max(0, Math.min(this.items.length - 1, index));
        var positionY = this.horz ? this.scrollPositionsByIndex[index].x : this.scrollPositionsByIndex[index].y;
		this.scrollToPercent(positionY / this.getScrollHeight(), instant);
    }

	scrollToPercent(p /*float*/, instant /*bool*/)
	{
		if (instant === undefined) instant = false;

		var maxScrollHeight = this.getScrollHeight();
		this.destinationScrollAmount = Math.trunc(maxScrollHeight * p);
		if (Engine.Utils.isNumberInvalid(this.destinationScrollAmount))
		{
			Debug.Log("Attempt to scroll to undefined!");
		}
		
		if (this.destinationScrollAmount > maxScrollHeight) this.destinationScrollAmount = Math.trunc(maxScrollHeight);
		if (this.destinationScrollAmount < 0) this.destinationScrollAmount = 0;
		
		if (instant)
		{
			this.currentScrollAmount = this.destinationScrollAmount;
			
			this.scrollPercent = 0;
			if (maxScrollHeight > 0) { this.scrollPercent = this.currentScrollAmount / maxScrollHeight; }
			
			if (this.horz)
			{
				this.itemClip.x = -Math.trunc(this.currentScrollAmount);
			}
			else
			{
				this.itemClip.y = -Math.trunc(this.currentScrollAmount);
			}
			
			this.updateVisible();
			this.throwOnScroll();
			this.needsUpdate = false;
		}
		else
		{
			this.isScrollToPercent = true;
			
			this.scrollAmount = Math.abs(this.destinationScrollAmount - this.currentScrollAmount) * 5;
			
			this.needsUpdate = true;
			this.scrollPercent = p;
		}
	}

	scrollToItemIndex(index /*int*/)
	{
		index = Math.max(0,Math.min(this.items.length - 1, index));
		
		var leftSide = this.horz ? this.items[index].x : this.items[index].y;
		var rightSide = this.horz ? (this.items[index].x + this.items[index].width) : (this.items[index].y + this.items[index].height);
		
		var scrollableHeight = this.getScrollHeight();
		var maxScroll = Math.min(scrollableHeight, leftSide) / scrollableHeight;
		var minScroll = Math.max(0, rightSide - this.visibleHeight) / scrollableHeight;
		
		if (this.scrollPercent > maxScroll)
		{
			this.scrollToPercent(maxScroll, true);
		}
		else if (this.scrollPercent < minScroll)
		{
			this.scrollToPercent(minScroll, true);
		}
	}

	scrollToRangeVisible(leftSide, rightSide)
    {
        var scrollableHeight = this.getScrollHeight();
        var maxScroll = Math.min(scrollableHeight, leftSide) / scrollableHeight;
        var minScroll = Math.max(0, rightSide - this.visibleHeight) / scrollableHeight;

        if (this.scrollPercent > maxScroll)
        {
            this.scrollToPercent(maxScroll, true);
        }
        else if (this.scrollPercent < minScroll)
        {
            this.scrollToPercent(minScroll, true);
        }

    }

	setScrollPercent(p /*float*/)
	{
		var maxScrollHeight = this.getScrollHeight();
		
		this.destinationScrollAmount = Math.trunc(maxScrollHeight * p);
		if (Engine.Utils.isNumberInvalid(this.destinationScrollAmount))
		{
			Debug.Log("Attempt to scroll to undefined!");
		}

		this.currentScrollAmount = this.destinationScrollAmount;

		this.needsUpdate = true;
		this.scrollPercent = p;
		
		this.throwOnScroll();
	}

	ensureScrollPositionValid()
	{
		if (this.vel != 0) return;

		var maxScrollHeight = this.getScrollHeight();
		var newScrollAmount = Math.max(0, Math.min(maxScrollHeight, this.destinationScrollAmount));
		
		this.destinationScrollAmount = Math.trunc(newScrollAmount);
		this.currentScrollAmount = this.destinationScrollAmount;
		
		this.needsUpdate = true;

		this.scrollPercent = 0;
		if (maxScrollHeight > 0) { this.scrollPercent = newScrollAmount / maxScrollHeight; }
	}

	setScrollVelocity(vel /*float*/)
	{
		this.vel = vel;
	}

	//px/s
	addScrollVelocity(vel /*float*/)
	{
		this.vel += vel;
		
		if (this.vel < -this.max_velocity) this.vel = -this.max_velocity;
		if (this.vel > this.max_velocity) this.vel = this.max_velocity;
		
		if (vel > 0) this.decelleration = Math.abs(this.baseDecelleration);
		if (vel < 0) this.decelleration = -Math.abs(this.baseDecelleration);
		
		//Debug.Log(this.vel);
	}

	update(timeElapsed /*float*/)
	{
		var maxScrollHeight = this.getScrollHeight();

		var noChange = (this.currentScrollAmount == this.destinationScrollAmount);
		
		//Destination based scroll update
		if ((this.vel == 0 && this.currentScrollAmount != this.destinationScrollAmount) || this.needsUpdate)
		{
			if (this.currentScrollAmount > this.destinationScrollAmount)
			{
				this.currentScrollAmount -= (timeElapsed * ((this.isScrollToPercent) ? this.scrollAmount : 200));
				if (this.currentScrollAmount < this.destinationScrollAmount)
				{
					this.isScrollToPercent = false;
					this.currentScrollAmount = this.destinationScrollAmount;
				}
			}
			
			if (this.currentScrollAmount < this.destinationScrollAmount)
			{
				this.currentScrollAmount += (timeElapsed * ((this.isScrollToPercent) ? this.scrollAmount : 200));
				if (this.currentScrollAmount > this.destinationScrollAmount)
				{
					this.isScrollToPercent = false;
					this.currentScrollAmount = this.destinationScrollAmount;
				}
			}
			
			this.scrollPercent = 0;
			if (maxScrollHeight > 0) this.scrollPercent = this.currentScrollAmount / maxScrollHeight;
			
			if (this.horz)
			{
				this.itemClip.x = -Math.trunc(this.currentScrollAmount);
			}
			else
			{
				this.itemClip.y = -Math.trunc(this.currentScrollAmount);
			}
			
			this.updateVisible();
			this.throwOnScroll();
			this.needsUpdate = false;
		}
		//Velocity based scroll update
		else if (this.vel != 0)
		{
			if (this.decelleration > 0 && this.vel > 0)
			{
				this.vel -= this.decelleration * timeElapsed;
				if (this.vel < 0) this.vel = 0;
			}
			else if (this.decelleration < 0 && this.vel < 0)
			{
				this.vel -= this.decelleration * timeElapsed;
				if (this.vel > 0) this.vel = 0;
			}
			
			this.currentScrollAmount += this.vel * timeElapsed;
			if (this.currentScrollAmount <= 0 || this.currentScrollAmount > maxScrollHeight)
			{
                this.decelleration *= Math.abs(this.decelleration);
                this.velocityScrolledPastEnd = true;
			}
			
       		if (this.vel == 0) this.currentScrollAmount = Math.trunc(this.currentScrollAmount);
			this.destinationScrollAmount = Math.trunc(this.currentScrollAmount);
			
			this.scrollPercent = 0;
			if (maxScrollHeight > 0) this.scrollPercent = this.currentScrollAmount / maxScrollHeight;
			
			if (this.horz)
			{
				this.itemClip.x = -Math.trunc(this.currentScrollAmount);
			}
			else
			{
				this.itemClip.y = -Math.trunc(this.currentScrollAmount);
			}
			
			this.updateVisible();
			this.throwOnScroll();
		}
		else if (this.velocityScrolledPastEnd)
		{
			this.velocityScrolledPastEnd = false;
			if (this.currentScrollAmount < 0) this.scrollToPercent(0);
			if (this.currentScrollAmount > this.maxScrollHeight) this.scrollToPercent(1);
		}
	}
}

Engine.ScrollBar = class extends Engine.Drawable
{
	constructor()
	{
		super();

		this.allowScroll = true;
		this.pane = null;
		this.minScrollPos = 0;
		this.maxScrollPos = 400;
		this.scrollWidthScale = 1.0;
		this.arrowHeight = 18;
		this.arrowWidth = 18;
		this.downArrowRotated = false;
		this.showWhenStill = true;
		this.showingScroller = true;
		this.fadeTween = new Engine.SimpleTween();
		this.fadeTimer = new Engine.SimpleTimer();
		this._scrollerMouseFlickEnabled = true;
		this.dragging = false;
		this._stopDragging = false;
		this.dragFactor = 1;
		this.dragStartY = 0;
		this.dragStartMouseY = 0;
		this.dragStartX = 0;
		this.dragStartMouseX = 0;
		this.touchDragging = false;
		this.scrolledPassedEnds = false;

		this.upArrow = new Engine.DrawableButton();
		this.downArrow = new Engine.DrawableButton();
		this.scrollBar = new Engine.Drawable();
		this.scroller = new Engine.Drawable();
		this.scrollerIcon = new Engine.Drawable();
		
		this.scrollBar.setNineRect();
		this.scroller.setNineRect();
		
		this.upArrow.onClick = () => { this.scrollUp(); };
		this.downArrow.onClick = () => { this.scrollDown(); };
		
        //this.scroller.interactive = true;
        this.scrollBar.mouseEnabled = true;
        this.scroller.mouseEnabled = true;
        this.scroller.events.onMouseDown.addListener(this, (event) => { this.beginDragScroll(event); });//('pointerdown', (event) => { this.beginDragScroll(event); } );

		//this.scroller.Events.MouseFlick = () => { this.flicked() };
	}

	setScrollPane(pane /*ScrollPaneBase*/)
	{
		this.pane = pane;
		pane.onScroll.addListener(this, this.scrollPaneScrolled);
	}

	init(arrowGraphic /*String*/, scrollbarGraphic /*String*/, scrollerGraphic /*String*/, scrollerOnly /*bool*/, height /*int*/, iconOnScroller /*string*/, showWhenStill /*bool*/)
	{
		if (arrowGraphic === undefined) arrowGraphic = "Chat_ScrollButton";//"ScrollButtonUp";
		if (scrollbarGraphic === undefined) scrollbarGraphic = "ScrollBG";
		if (scrollerGraphic === undefined) scrollerGraphic = "InventoryScroller";//"Button_TinyDarkGray";
		if (scrollerOnly === undefined) scrollerOnly = false;
		if (height === undefined) height = 450;
		if (iconOnScroller === undefined) iconOnScroller = null;
		if (showWhenStill === undefined) showWhenStill = true;

		this.upArrow.init(arrowGraphic, "");
		
		this.arrowHeight = Math.trunc(this.upArrow.height);
		this.arrowWidth = Math.trunc(this.upArrow.width);
		
		this.downArrow.init(arrowGraphic,"");
		// downArrow.y = height - (2 * arrowHeight);
		
		// rotate the arrow so its facing down and adjust the coordinates.
		// Y position will be adjusted in the setHeight function
		this.downArrowRotated = true;
		this.downArrow.scale.y = -1;
		
		this.scrollBar.loadFromGraphicName(scrollbarGraphic);
		this.scroller.loadFromGraphicName(scrollerGraphic);
		
		this.setScrollerIcon(iconOnScroller);
		this.setScrollerOnly(scrollerOnly);
		
		this.addChild(this.scroller);
		this.setHeight(height);

		this.showWhenStill = showWhenStill;
		if (!this.showWhenStill)
		{
			this.showingScroller = false;
			this.scroller.alpha = 0;
		}
	}

	setScrollerOnly(scrollerOnly /*bool*/)
	{
		if(!scrollerOnly)
		{
			this.addChild(this.scrollBar);
			this.addChild(this.downArrow);
			this.addChild(this.upArrow);
			this.arrowHeight = Math.trunc(this.upArrow.height);
		}
		else
		{
			this.removeChild(this.scrollBar);
			this.removeChild(this.downArrow);
			this.removeChild(this.upArrow);
			this.arrowHeight = 0;
		}
	}

	removeScrollBackground()
	{
		this.scrollBar.removeFromParent();
	}

	setScrollerIcon(name /*string*/)
	{
		if(name != null)
		{
			this.scroller.addChild(this.scrollerIcon);
			this.scrollerIcon.loadFromGraphicName(name);
			
			this.scrollerIcon.x = (this.scroller.width - this.scrollerIcon.width) / 2;
			this.scrollerIcon.y = (this.scroller.height - this.scrollerIcon.height) / 2;
		}
		else
		{
			this.scrollerIcon.removeFromParent();
		}
	}

	setScrollWidthScale(scale /*float*/)
	{
		this.scrollWidthScale = scale;
	}

	setHeight(height /*int*/, scrollBarWidth /*int*/, buttonYSpacing /*int*/, scrollerHeight /*int*/)
	{
		if (scrollBarWidth === undefined) scrollBarWidth = 0;
		if (buttonYSpacing === undefined) buttonYSpacing = 0;
		if (scrollerHeight === undefined) scrollerHeight = 0;

		if (this.downArrowRotated)
		{
			this.downArrow.y = height;
		}
		else
		{
			this.downArrow.y = height - this.arrowHeight;
		}
		
		this.scrollBar.y = this.arrowHeight + buttonYSpacing;
		this.scrollBar.height = height - (2 * (this.arrowHeight + buttonYSpacing));
		this.scrollBar.width = scrollBarWidth == 0 ? this.arrowWidth * this.scrollWidthScale : scrollBarWidth;
		this.scrollBar.x = (this.upArrow.width / 2) - (this.scrollBar.width / 2);
		
		var minScrollbarSize = 22;
		
		this.scroller.height = scrollerHeight == 0 ? Math.max(this.scrollBar.height * 0.1, minScrollbarSize) : scrollerHeight;
		this.scroller.width = scrollBarWidth == 0 ? this.arrowWidth * this.scrollWidthScale : scrollBarWidth;
		this.scroller.x = this.upArrow.parent != null ? (this.upArrow.width / 2) - (this.scroller.width / 2) : 0;
		
		this.minScrollPos = Math.trunc(this.scrollBar.y);
		this.maxScrollPos = Math.trunc((this.scrollBar.height + this.scrollBar.y) - (this.scroller.height));
		
		this.scroller.y = this.minScrollPos + Math.trunc((this.maxScrollPos - this.minScrollPos) * this.pane.scrollPercent);
		
		this.scrollerIcon.x = (this.scroller.width - this.scrollerIcon.width) / 2;
		this.scrollerIcon.y = (this.scroller.height - this.scrollerIcon.height) / 2;
	}

    scrollerMouseFlickEnabled(enabled)
    {
    	return; // no flicking on webGL
        if (this._scrollerMouseFlickEnabled == enabled) return;
        if (enabled) scroller.events.mouseFlick.addListener(this, this.flicked);
        if (!enabled) scroller.events.mouseFlick.removeListener(this);
        this._scrollerMouseFlickEnabled = enabled;
    }

	flicked(d /*Drawable*/, v /*Vector2*/)
	{
		if (!this.allowScroll) return;
        if (this.scrolledPassedEnds) return;
		if (this.pane.horz)
		{
			this.pane.addScrollVelocity(v.x * this.dragFactor * 100);
		}
		else
		{
			this.pane.addScrollVelocity(-v.y * this.dragFactor * 100);
		}
	}

	mouseWheel(velocity /*float*/)
	{
		if (!this.allowScroll) return;
		
        var scrollHeight = this.pane.getScrollHeight();
        var pixelsPerPercent = scrollHeight / 100;
		var scrollPixelsPerClick = 66;
		var percentPerClick = 0;
        if (scrollHeight > 0) { var percentPerClick = scrollPixelsPerClick / pixelsPerPercent / 100; }
		
		var currentScrollPercent = this.pane.scrollPercent;
		var newPercent = currentScrollPercent + (velocity * percentPerClick * 10);
		newPercent = Math.max(0, Math.min(1, newPercent));
		this.pane.setScrollPercent(newPercent);
	}

	scrollLeft() { this.pane.scrollLeft(); }

	scrollRight() { this.pane.scrollRight(); }

	scrollUp() { this.pane.scrollUp(); }

	scrollDown() { this.pane.scrollDown(); }

	get stopDragging()
	{
		return this._stopDragging;
	}
	set stopDragging(value)
	{
		this._stopDragging = value;
	}

	beginDragScroll(d /*Drawable*/)
	{
		if (!this.allowScroll) return;

		var mousePos = Engine.Mouse.position;

		this.dragStartMouseY = Math.trunc(mousePos.y);
		this.dragStartY = Math.trunc(this.scroller.y);
		
		this.dragStartMouseX = Math.trunc(mousePos.x);
		this.dragStartX = Math.trunc(this.scroller.x);
		
		this.dragging = true;
		Engine.EnterFrameManagerInstance.remove(this);
		Engine.EnterFrameManagerInstance.add(this, this.update);
		this.pane.setScrollVelocity(0);
		this.dragFactor = 1;
		this.touchDragging = false;
	}

	beginTouchScroll(d /*Drawable*/)
	{
		if (!this.allowScroll) return;
		
		this.beginDragScroll(d);
		this.dragFactor = -1;
		this.touchDragging = true;
	}

	beginDragScrollReverse(d /*Drawable*/)
	{
		if (!this.allowScroll) return;
		
		this.beginDragScroll(d);
		this.dragFactor = -1;
	}

	update(timeElapsed /*float*/)
	{
		if (!this.allowScroll) return;
		if (this.dragging)
		{
			var mousePos = Engine.Mouse.position;
			//var activeInteraction = Engine.Pixi.renderer.plugins.interaction.activeInteraction;

			if (Engine.Mouse.mouseButtonDown[0] && !this.stopDragging)
			{
				var dragDif = Math.trunc((mousePos.y - this.dragStartMouseY) * this.dragFactor);
				if(this.pane.horz) dragDif = Math.trunc((mousePos.x - this.dragStartMouseX) * this.dragFactor);
				
				var range = (this.maxScrollPos - this.minScrollPos);
				
				if(range == 0)
				{
					this.dragging = false;
					this.stopDragging = false;
					this.pane.ensureScrollPositionValid();
					Engine.EnterFrameManagerInstance.remove(this);
					return;
				}
				
				if (this.touchDragging)
				{
					//convert the motion so it is a 1:1 mapping with finger movement on the scroll area.
					var totalHeight = this.pane.getScrollHeight();
					if (totalHeight <= 0) { dragDif = 0; }
					else { dragDif = dragDif * (range / totalHeight); }
				}
				
				this.scroller.y = this.dragStartY + dragDif;
				
				if (this.scroller.y < this.minScrollPos) this.scroller.y = this.minScrollPos;
				if (this.scroller.y > this.maxScrollPos) this.scroller.y = this.maxScrollPos;
				
				this.scrollerIcon.x = (this.scroller.width - this.scrollerIcon.width) / 2;
				this.scrollerIcon.y = (this.scroller.height - this.scrollerIcon.height) / 2;
				
				//var percent = (this.scroller.y - this.minScrollPos) / range;
                var realScrollAmount = (this.dragStartY + dragDif - this.minScrollPos);
                this.scrolledPassedEnds = false;
                if (realScrollAmount < 0  || realScrollAmount > range)
                {
                    this.scrolledPassedEnds = true;
                    
                    var easedDistance = realScrollAmount > range ? realScrollAmount - range : Math.abs(realScrollAmount);
                    var preeased = easedDistance;

                    if (this.pane.horz)
                    {
                    	easedDistance = (1 + Math.log10((5 + easedDistance) / 50)) * 20;
                    }
                    else
                    {
                    	easedDistance = (1 + Math.log10((5 + easedDistance) / 50)) * 3;
                    }

                    realScrollAmount = realScrollAmount > range ? range + easedDistance : -easedDistance;
                }

                var realScrollPercent = realScrollAmount / range;
                if (dragDif != 0) this.pane.setScrollPercent(realScrollPercent);

                if (!this.showWhenStill) this.showScroller();
			}
			else
			{
				this.dragging = false;

				// TODO return the scroll to the 0 postion if it is offkilter
                if (this.scrolledPassedEnds)
                {
                    if (this.pane.scrollPercent < 0) this.pane.scrollToPercent(0);
                    if (this.pane.scrollPercent > 1) this.pane.scrollToPercent(1);
                }
                else
                {
                    this.pane.ensureScrollPositionValid();
                }

				this.stopDragging = false;
				Engine.EnterFrameManagerInstance.remove(this);
			}
		}
	}

	scrollPaneScrolled(s /*ScrollPaneBase*/)
	{
		if (!this.dragging)
		{
			this.updateScrollPos();
		}
	}

	updateScrollPos()
	{
		var percent = Math.max(0, Math.min(this.pane.scrollPercent, 1));
		this.scroller.y = this.minScrollPos + ((this.maxScrollPos - this.minScrollPos) * percent);
		this.scrollerIcon.x = (this.scroller.width - this.scrollerIcon.width) / 2;
		this.scrollerIcon.y = (this.scroller.height - this.scrollerIcon.height) / 2;

		if (!this.showWhenStill) this.showScroller();
	}

	showScroller()
	{
		if (!this.showingScroller)
		{
			this.fadeTween.init((a) => this.scroller.alpha = a, Engine.SimpleTween.easeLinear, this.scroller.alpha, 1, 0.15);
            this.showingScroller = true;
        }
        this.fadeTimer.start(1, () =>
	        {
	            this.fadeTween.Init((a) => this.scroller.alpha = a, Engine.SimpleTween.easeLinear, this.scroller.alpha, 0, 0.5);
	            this.showingScroller = false;
	        }
        );
	}
}