Files
SmallGame_Cocos2dx/Classes/CCScrollviewSimple.cpp
T
2014-08-20 16:15:02 +08:00

788 lines
22 KiB
C++

//
// CCScrollviewSimple.cpp
// OnceAgain
//
// Created by mac on 14-4-18.
//
//
#include "CCScrollviewSimple.h"
#define SCROLL_DEACCEL_RATE 0.95f
#define SCROLL_DEACCEL_DIST 1.0f
#define BOUNCE_DURATION 0.45f
#define INSET_RATIO 0.4f
#define MOVE_INCH 10.0f/160.0f
static float convertDistanceFromPointToInch(float pointDis)
{
float factor = ( CCEGLView::sharedOpenGLView()->getScaleX() + CCEGLView::sharedOpenGLView()->getScaleY() ) / 2;
return pointDis * factor / CCDevice::getDPI();
}
CCScrollviewSimple::CCScrollviewSimple()
: m_fZoomScale(0.0f)
, m_fMinZoomScale(0.0f)
, m_fMaxZoomScale(0.0f)
, m_pDelegate(NULL)
, m_eDirection(kCCScrollViewDirectionBoth)
, m_bDragging(false)
, m_pContainer(NULL)
, m_bTouchMoved(false)
, m_bBounceable(false)
, m_bClippingToBounds(false)
, m_fTouchLength(0.0f)
, m_pTouches(NULL)
, m_fMinScale(0.0f)
, m_fMaxScale(0.0f)
{
}
CCScrollviewSimple::~CCScrollviewSimple()
{
CC_SAFE_RELEASE(m_pTouches);
this->unregisterScriptHandler(kScrollViewScroll);
this->unregisterScriptHandler(kScrollViewZoom);
}
CCScrollviewSimple* CCScrollviewSimple::create(CCSize size, CCNode* container/* = NULL*/)
{
CCScrollviewSimple* pRet = new CCScrollviewSimple();
if (pRet && pRet->initWithViewSize(size, container))
{
pRet->autorelease();
}
else
{
CC_SAFE_DELETE(pRet);
}
return pRet;
}
CCScrollviewSimple* CCScrollviewSimple::create()
{
CCScrollviewSimple* pRet = new CCScrollviewSimple();
if (pRet && pRet->init())
{
pRet->autorelease();
}
else
{
CC_SAFE_DELETE(pRet);
}
return pRet;
}
bool CCScrollviewSimple::initWithViewSize(CCSize size, CCNode *container/* = NULL*/)
{
if (CCLayer::init())
{
m_pContainer = container;
if (!this->m_pContainer)
{
m_pContainer = CCLayer::create();
this->m_pContainer->ignoreAnchorPointForPosition(false);
this->m_pContainer->setAnchorPoint(ccp(0.0f, 0.0f));
}
this->setViewSize(size);
setTouchEnabled(true);
m_pTouches = new CCArray();
m_pDelegate = NULL;
m_bBounceable = true;
m_bClippingToBounds = true;
//m_pContainer->setContentSize(CCSizeZero);
m_eDirection = kCCScrollViewDirectionBoth;
m_pContainer->setPosition(ccp(0.0f, 0.0f));
m_fTouchLength = 0.0f;
this->addChild(m_pContainer);
m_fMinScale = m_fMaxScale = 1.0f;
m_mapScriptHandler.clear();
return true;
}
return false;
}
bool CCScrollviewSimple::init()
{
return this->initWithViewSize(CCSizeMake(200, 200), NULL);
}
void CCScrollviewSimple::registerWithTouchDispatcher()
{
CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, CCLayer::getTouchPriority(), false);
}
bool CCScrollviewSimple::isNodeVisible(CCNode* node)
{
const CCPoint offset = this->getContentOffset();
const CCSize size = this->getViewSize();
const float scale = this->getZoomScale();
CCRect viewRect;
viewRect = CCRectMake(-offset.x/scale, -offset.y/scale, size.width/scale, size.height/scale);
return viewRect.intersectsRect(node->boundingBox());
}
void CCScrollviewSimple::pause(CCObject* sender)
{
m_pContainer->pauseSchedulerAndActions();
CCObject* pObj = NULL;
CCArray* pChildren = m_pContainer->getChildren();
CCARRAY_FOREACH(pChildren, pObj)
{
CCNode* pChild = (CCNode*)pObj;
pChild->pauseSchedulerAndActions();
}
}
void CCScrollviewSimple::resume(CCObject* sender)
{
CCObject* pObj = NULL;
CCArray* pChildren = m_pContainer->getChildren();
CCARRAY_FOREACH(pChildren, pObj)
{
CCNode* pChild = (CCNode*)pObj;
pChild->resumeSchedulerAndActions();
}
m_pContainer->resumeSchedulerAndActions();
}
void CCScrollviewSimple::setTouchEnabled(bool e)
{
CCLayer::setTouchEnabled(e);
if (!e)
{
m_bDragging = false;
m_bTouchMoved = false;
m_pTouches->removeAllObjects();
}
}
void CCScrollviewSimple::setContentOffset(CCPoint offset, bool animated/* = false*/)
{
if (animated)
{ //animate scrolling
this->setContentOffsetInDuration(offset, BOUNCE_DURATION);
}
else
{ //set the container position directly
if (!m_bBounceable)
{
const CCPoint minOffset = this->minContainerOffset();
const CCPoint maxOffset = this->maxContainerOffset();
offset.x = MAX(minOffset.x, MIN(maxOffset.x, offset.x));
offset.y = MAX(minOffset.y, MIN(maxOffset.y, offset.y));
}
m_pContainer->setPosition(offset);
if (m_pDelegate != NULL)
{
m_pDelegate->scrollViewDidScroll(this);
}
}
}
void CCScrollviewSimple::setContentOffsetInDuration(CCPoint offset, float dt)
{
CCFiniteTimeAction *scroll, *expire;
scroll = CCMoveTo::create(dt, offset);
expire = CCCallFuncN::create(this, callfuncN_selector(CCScrollviewSimple::stoppedAnimatedScroll));
m_pContainer->runAction(CCSequence::create(scroll, expire, NULL));
this->schedule(schedule_selector(CCScrollviewSimple::performedAnimatedScroll));
}
CCPoint CCScrollviewSimple::getContentOffset()
{
return m_pContainer->getPosition();
}
void CCScrollviewSimple::setZoomScale(float s)
{
if (m_pContainer->getScale() != s)
{
CCPoint oldCenter, newCenter;
CCPoint center;
if (m_fTouchLength == 0.0f)
{
center = ccp(m_tViewSize.width*0.5f, m_tViewSize.height*0.5f);
center = this->convertToWorldSpace(center);
}
else
{
center = m_tTouchPoint;
}
oldCenter = m_pContainer->convertToNodeSpace(center);
m_pContainer->setScale(MAX(m_fMinScale, MIN(m_fMaxScale, s)));
newCenter = m_pContainer->convertToWorldSpace(oldCenter);
const CCPoint offset = ccpSub(center, newCenter);
if (m_pDelegate != NULL)
{
m_pDelegate->scrollViewDidZoom(this);
}
this->setContentOffset(ccpAdd(m_pContainer->getPosition(),offset));
}
}
float CCScrollviewSimple::getZoomScale()
{
return m_pContainer->getScale();
}
void CCScrollviewSimple::setZoomScale(float s, bool animated)
{
if (animated)
{
this->setZoomScaleInDuration(s, BOUNCE_DURATION);
}
else
{
this->setZoomScale(s);
}
}
void CCScrollviewSimple::setZoomScaleInDuration(float s, float dt)
{
if (dt > 0)
{
if (m_pContainer->getScale() != s)
{
CCActionTween *scaleAction;
scaleAction = CCActionTween::create(dt, "zoomScale", m_pContainer->getScale(), s);
this->runAction(scaleAction);
}
}
else
{
this->setZoomScale(s);
}
}
void CCScrollviewSimple::setViewSize(CCSize size)
{
m_tViewSize = size;
CCLayer::setContentSize(size);
}
CCNode * CCScrollviewSimple::getContainer()
{
return this->m_pContainer;
}
void CCScrollviewSimple::setContainer(CCNode * pContainer)
{
// Make sure that 'm_pContainer' has a non-NULL value since there are
// lots of logic that use 'm_pContainer'.
if (NULL == pContainer)
return;
this->removeAllChildrenWithCleanup(true);
this->m_pContainer = pContainer;
this->m_pContainer->ignoreAnchorPointForPosition(false);
this->m_pContainer->setAnchorPoint(ccp(0.0f, 0.0f));
this->addChild(this->m_pContainer);
this->setViewSize(this->m_tViewSize);
}
void CCScrollviewSimple::relocateContainer(bool animated)
{
CCPoint oldPoint, min, max;
float newX, newY;
min = this->minContainerOffset();
max = this->maxContainerOffset();
oldPoint = m_pContainer->getPosition();
newX = oldPoint.x;
newY = oldPoint.y;
if (m_eDirection == kCCScrollViewDirectionBoth || m_eDirection == kCCScrollViewDirectionHorizontal)
{
newX = MAX(newX, min.x);
newX = MIN(newX, max.x);
}
if (m_eDirection == kCCScrollViewDirectionBoth || m_eDirection == kCCScrollViewDirectionVertical)
{
newY = MIN(newY, max.y);
newY = MAX(newY, min.y);
}
if (newY != oldPoint.y || newX != oldPoint.x)
{
this->setContentOffset(ccp(newX, newY), animated);
}
}
CCPoint CCScrollviewSimple::maxContainerOffset()
{
return ccp(0.0f, 0.0f);
}
CCPoint CCScrollviewSimple::minContainerOffset()
{
return ccp(m_tViewSize.width - m_pContainer->getContentSize().width*m_pContainer->getScaleX(),
m_tViewSize.height - m_pContainer->getContentSize().height*m_pContainer->getScaleY());
}
void CCScrollviewSimple::deaccelerateScrolling(float dt)
{
if (m_bDragging)
{
this->unschedule(schedule_selector(CCScrollviewSimple::deaccelerateScrolling));
return;
}
float newX, newY;
CCPoint maxInset, minInset;
m_pContainer->setPosition(ccpAdd(m_pContainer->getPosition(), m_tScrollDistance));
if (m_bBounceable)
{
maxInset = m_fMaxInset;
minInset = m_fMinInset;
}
else
{
maxInset = this->maxContainerOffset();
minInset = this->minContainerOffset();
}
//check to see if offset lies within the inset bounds
newX = MIN(m_pContainer->getPosition().x, maxInset.x);
newX = MAX(newX, minInset.x);
newY = MIN(m_pContainer->getPosition().y, maxInset.y);
newY = MAX(newY, minInset.y);
newX = m_pContainer->getPosition().x;
newY = m_pContainer->getPosition().y;
m_tScrollDistance = ccpSub(m_tScrollDistance, ccp(newX - m_pContainer->getPosition().x, newY - m_pContainer->getPosition().y));
m_tScrollDistance = ccpMult(m_tScrollDistance, SCROLL_DEACCEL_RATE);
this->setContentOffset(ccp(newX,newY));
if ((fabsf(m_tScrollDistance.x) <= SCROLL_DEACCEL_DIST &&
fabsf(m_tScrollDistance.y) <= SCROLL_DEACCEL_DIST) ||
newY > maxInset.y || newY < minInset.y ||
newX > maxInset.x || newX < minInset.x ||
newX == maxInset.x || newX == minInset.x ||
newY == maxInset.y || newY == minInset.y)
{
this->unschedule(schedule_selector(CCScrollviewSimple::deaccelerateScrolling));
this->relocateContainer(true);
}
}
void CCScrollviewSimple::stoppedAnimatedScroll(CCNode * node)
{
this->unschedule(schedule_selector(CCScrollviewSimple::performedAnimatedScroll));
// After the animation stopped, "scrollViewDidScroll" should be invoked, this could fix the bug of lack of tableview cells.
if (m_pDelegate != NULL)
{
m_pDelegate->scrollViewDidScroll(this);
}
}
void CCScrollviewSimple::performedAnimatedScroll(float dt)
{
if (m_bDragging)
{
this->unschedule(schedule_selector(CCScrollviewSimple::performedAnimatedScroll));
return;
}
if (m_pDelegate != NULL)
{
m_pDelegate->scrollViewDidScroll(this);
}
}
const CCSize& CCScrollviewSimple::getContentSize() const
{
return m_pContainer->getContentSize();
}
void CCScrollviewSimple::setContentSize(const CCSize & size)
{
if (this->getContainer() != NULL)
{
this->getContainer()->setContentSize(size);
this->updateInset();
}
}
void CCScrollviewSimple::updateInset()
{
if (this->getContainer() != NULL)
{
m_fMaxInset = this->maxContainerOffset();
m_fMaxInset = ccp(m_fMaxInset.x + m_tViewSize.width * INSET_RATIO,
m_fMaxInset.y + m_tViewSize.height * INSET_RATIO);
m_fMinInset = this->minContainerOffset();
m_fMinInset = ccp(m_fMinInset.x - m_tViewSize.width * INSET_RATIO,
m_fMinInset.y - m_tViewSize.height * INSET_RATIO);
}
}
/**
* make sure all children go to the container
*/
void CCScrollviewSimple::addChild(CCNode * child, int zOrder, int tag)
{
child->ignoreAnchorPointForPosition(false);
child->setAnchorPoint(ccp(0.0f, 0.0f));
if (m_pContainer != child) {
m_pContainer->addChild(child, zOrder, tag);
} else {
CCLayer::addChild(child, zOrder, tag);
}
}
void CCScrollviewSimple::addChild(CCNode * child, int zOrder)
{
this->addChild(child, zOrder, child->getTag());
}
void CCScrollviewSimple::addChild(CCNode * child)
{
this->addChild(child, child->getZOrder(), child->getTag());
}
/**
* clip this view so that outside of the visible bounds can be hidden.
*/
void CCScrollviewSimple::beforeDraw()
{
if (m_bClippingToBounds)
{
m_bScissorRestored = false;
CCRect frame = getViewRect();
if (CCEGLView::sharedOpenGLView()->isScissorEnabled()) {
m_bScissorRestored = true;
m_tParentScissorRect = CCEGLView::sharedOpenGLView()->getScissorRect();
//set the intersection of m_tParentScissorRect and frame as the new scissor rect
if (frame.intersectsRect(m_tParentScissorRect)) {
float x = MAX(frame.origin.x, m_tParentScissorRect.origin.x);
float y = MAX(frame.origin.y, m_tParentScissorRect.origin.y);
float xx = MIN(frame.origin.x+frame.size.width, m_tParentScissorRect.origin.x+m_tParentScissorRect.size.width);
float yy = MIN(frame.origin.y+frame.size.height, m_tParentScissorRect.origin.y+m_tParentScissorRect.size.height);
CCEGLView::sharedOpenGLView()->setScissorInPoints(x, y, xx-x, yy-y);
}
}
else {
glEnable(GL_SCISSOR_TEST);
CCEGLView::sharedOpenGLView()->setScissorInPoints(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
}
}
}
/**
* retract what's done in beforeDraw so that there's no side effect to
* other nodes.
*/
void CCScrollviewSimple::afterDraw()
{
if (m_bClippingToBounds)
{
if (m_bScissorRestored) {//restore the parent's scissor rect
CCEGLView::sharedOpenGLView()->setScissorInPoints(m_tParentScissorRect.origin.x, m_tParentScissorRect.origin.y, m_tParentScissorRect.size.width, m_tParentScissorRect.size.height);
}
else {
glDisable(GL_SCISSOR_TEST);
}
}
}
void CCScrollviewSimple::visit()
{
// quick return if not visible
if (!isVisible())
{
return;
}
kmGLPushMatrix();
if (m_pGrid && m_pGrid->isActive())
{
m_pGrid->beforeDraw();
this->transformAncestors();
}
this->transform();
this->beforeDraw();
if(m_pChildren)
{
ccArray *arrayData = m_pChildren->data;
unsigned int i=0;
// draw children zOrder < 0
for( ; i < arrayData->num; i++ )
{
CCNode *child = (CCNode*)arrayData->arr[i];
if ( child->getZOrder() < 0 )
{
child->visit();
}
else
{
break;
}
}
// this draw
this->draw();
// draw children zOrder >= 0
for( ; i < arrayData->num; i++ )
{
CCNode* child = (CCNode*)arrayData->arr[i];
child->visit();
}
}
else
{
this->draw();
}
this->afterDraw();
if ( m_pGrid && m_pGrid->isActive())
{
m_pGrid->afterDraw(this);
}
kmGLPopMatrix();
}
bool CCScrollviewSimple::ccTouchBegan(CCTouch* touch, CCEvent* event)
{
if (!this->isVisible())
{
return false;
}
CCRect frame = getViewRect();
//dispatcher does not know about clipping. reject touches outside visible bounds.
if (m_pTouches->count() > 2 ||
m_bTouchMoved ||
!frame.containsPoint(m_pContainer->convertToWorldSpace(m_pContainer->convertTouchToNodeSpace(touch))))
{
return false;
}
if (!m_pTouches->containsObject(touch))
{
m_pTouches->addObject(touch);
}
if (m_pTouches->count() == 1)
{ // scrolling
m_tTouchPoint = this->convertTouchToNodeSpace(touch);
m_bTouchMoved = false;
m_bDragging = true; //dragging started
m_tScrollDistance = ccp(0.0f, 0.0f);
m_fTouchLength = 0.0f;
}
else if (m_pTouches->count() == 2)
{
m_tTouchPoint = ccpMidpoint(this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)),
this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1)));
m_fTouchLength = ccpDistance(m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)),
m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1)));
m_bDragging = false;
}
return true;
}
void CCScrollviewSimple::ccTouchMoved(CCTouch* touch, CCEvent* event)
{
if (!this->isVisible())
{
return;
}
if (m_pTouches->containsObject(touch))
{
if (m_pTouches->count() == 1 && m_bDragging)
{ // scrolling
CCPoint moveDistance, newPoint, maxInset, minInset;
CCRect frame;
float newX, newY;
frame = getViewRect();
newPoint = this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0));
moveDistance = ccpSub(newPoint, m_tTouchPoint);
float dis = 0.0f;
if (m_eDirection == kCCScrollViewDirectionVertical)
{
dis = moveDistance.y;
}
else if (m_eDirection == kCCScrollViewDirectionHorizontal)
{
dis = moveDistance.x;
}
else
{
dis = sqrtf(moveDistance.x*moveDistance.x + moveDistance.y*moveDistance.y);
}
if (!m_bTouchMoved && fabs(convertDistanceFromPointToInch(dis)) < MOVE_INCH )
{
//CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y);
return;
}
if (!m_bTouchMoved)
{
moveDistance = CCPointZero;
}
m_tTouchPoint = newPoint;
m_bTouchMoved = true;
if (frame.containsPoint(this->convertToWorldSpace(newPoint)))
{
switch (m_eDirection)
{
case kCCScrollViewDirectionVertical:
moveDistance = ccp(0.0f, moveDistance.y);
break;
case kCCScrollViewDirectionHorizontal:
moveDistance = ccp(moveDistance.x, 0.0f);
break;
default:
break;
}
maxInset = m_fMaxInset;
minInset = m_fMinInset;
newX = m_pContainer->getPosition().x + moveDistance.x;
newY = m_pContainer->getPosition().y + moveDistance.y;
m_tScrollDistance = moveDistance;
this->setContentOffset(ccp(newX, newY));
}
}
else if (m_pTouches->count() == 2 && !m_bDragging)
{
const float len = ccpDistance(m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)),
m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1)));
this->setZoomScale(this->getZoomScale()*len/m_fTouchLength);
}
}
}
void CCScrollviewSimple::ccTouchEnded(CCTouch* touch, CCEvent* event)
{
if (!this->isVisible())
{
return;
}
if (m_pTouches->containsObject(touch))
{
if (m_pTouches->count() == 1 && m_bTouchMoved)
{
this->schedule(schedule_selector(CCScrollviewSimple::deaccelerateScrolling));
}
m_pTouches->removeObject(touch);
}
if (m_pTouches->count() == 0)
{
m_bDragging = false;
m_bTouchMoved = false;
}
}
void CCScrollviewSimple::ccTouchCancelled(CCTouch* touch, CCEvent* event)
{
if (!this->isVisible())
{
return;
}
m_pTouches->removeObject(touch);
if (m_pTouches->count() == 0)
{
m_bDragging = false;
m_bTouchMoved = false;
}
}
CCRect CCScrollviewSimple::getViewRect()
{
CCPoint screenPos = this->convertToWorldSpace(CCPointZero);
float scaleX = this->getScaleX();
float scaleY = this->getScaleY();
for (CCNode *p = m_pParent; p != NULL; p = p->getParent()) {
scaleX *= p->getScaleX();
scaleY *= p->getScaleY();
}
// Support negative scaling. Not doing so causes intersectsRect calls
// (eg: to check if the touch was within the bounds) to return false.
// Note, CCNode::getScale will assert if X and Y scales are different.
if(scaleX<0.f) {
screenPos.x += m_tViewSize.width*scaleX;
scaleX = -scaleX;
}
if(scaleY<0.f) {
screenPos.y += m_tViewSize.height*scaleY;
scaleY = -scaleY;
}
return CCRectMake(screenPos.x, screenPos.y, m_tViewSize.width*scaleX, m_tViewSize.height*scaleY);
}
void CCScrollviewSimple::registerScriptHandler(int nFunID,int nScriptEventType)
{
this->unregisterScriptHandler(nScriptEventType);
m_mapScriptHandler[nScriptEventType] = nFunID;
}
void CCScrollviewSimple::unregisterScriptHandler(int nScriptEventType)
{
std::map<int,int>::iterator iter = m_mapScriptHandler.find(nScriptEventType);
if (m_mapScriptHandler.end() != iter)
{
m_mapScriptHandler.erase(iter);
}
}
int CCScrollviewSimple::getScriptHandler(int nScriptEventType)
{
std::map<int,int>::iterator iter = m_mapScriptHandler.find(nScriptEventType);
if (m_mapScriptHandler.end() != iter)
return iter->second;
return 0;
}