ToyDragAndDropHandler.cpp 14.8 KB

//
//  ToyDragAndDropHandler.cpp
//  SteveMaggieCpp
//
//  Created by Katarzyna Kalinowska-Górska on 02.06.2017.
//
//

#include <stdio.h>
#include "ToyDragAndDropHandler.h"
#include "ToyMiscUtils.h"
#include "ToySpriteAnimator.h"
#include "ToyGeometryUtils.h"

ToyDragAndDropHandler::ToyDragAndDropHandler(std::vector<cocos2d::Node*> objectsToDrag, cocos2d::Node* destinationObject, std::vector<cocos2d::Node*> wrongDraggedObjects, std::vector<cocos2d::Node*> wrongDestinationObjects)
{
    _objectsToDrag = objectsToDrag;
    _destinationObject = destinationObject;
    _wrongDraggedObjects = wrongDraggedObjects;
    _wrongDestinationObjects = wrongDestinationObjects;
    _extendPercentValues = std::vector<float>(_objectsToDrag.size(), 0);

    _draggedObject = NULL;

    _allowDraggingWrongObjects = false;
    _allowDraggingInvisibleObjects = false;
    _allowMultipleDrag = false;
    _moreAccurateDestinationCheck = false;
    _fingerOnlyDestinationCheck = false;
    
    _currentDraggingState = NotDragging;
    _draggingWrongObject = false;
    _draggingOverWrongDestinationObject = NULL;
    
    _finalSuccessCallback = [](cocos2d::Node*, cocos2d::Node*){};
    _successCallback = [](cocos2d::Node*, cocos2d::Node*){};
    _wrongDestinationObjectCallback = [](cocos2d::Node*){};
    _startDraggingWrongObjectCallback = [](cocos2d::Node*){};
    _startDraggingCorrectObjectCallback = [](cocos2d::Node*){};
    _draggedObjectEnterCorrectDestinationCallback = [](cocos2d::Node*, cocos2d::Node*){};
    _draggedObjectEnterWrongDestinationCallback = [](cocos2d::Node*, cocos2d::Node*){};
    _dropTooSoonCallback = [](){};
    _draggedWrongObjectCallback = [](cocos2d::Node*){};

    _objectDragNDropSuccessBehaviour = [](cocos2d::Node*, cocos2d::Node*, cocos2d::Point){};
}

void ToyDragAndDropHandler::setObjectToDragExtendPercentValues(std::vector<float> values)
{
    _extendPercentValues = values;
}

void ToyDragAndDropHandler::setAccurateDestinationCheck(bool accurateDstCheck)
{
    _moreAccurateDestinationCheck = accurateDstCheck;
}

void ToyDragAndDropHandler::setFingerOnlyDestinationCheck(bool fingerOnlyDstCheck)
{
    _fingerOnlyDestinationCheck = fingerOnlyDstCheck;
}

TouchHandlerFunction ToyDragAndDropHandler::prepareTouchBeganHandler()
{
    return [&](cocos2d::Touch* touch, cocos2d::Event* event){
        
        for(int d = 0; d < _objectsToDrag.size(); ++d){
        
            if(!_allowDraggingInvisibleObjects && _objectsToDrag[d]->getOpacity() == 0){
                continue;
            }
        
            auto grabArea = ToyMiscUtils::getExtendedActiveArea(_objectsToDrag[d], _extendPercentValues[d], _extendPercentValues[d]);
        
            if(grabArea.containsPoint(touch->getLocation())){
            
                _draggedObject = _objectsToDrag[d];
                _draggedObjectOriginalZ = _draggedObject->getLocalZOrder();//getZOrder();
                _startLocation = touch->getLocation();
                _startingObjectPosition = _draggedObject->getPosition();
                
                for(int i = ToySpriteAnimator::RF_ANIMATION_TAG_RANGE_MIN; i <= ToySpriteAnimator::RF_ANIMATION_TAG_RANGE_MAX; ++i){
                    
                    auto action = _draggedObject->getActionByTag(i);
                    if(action){
                        _cachedDraggedObjectRFActionTags.push_back(i);
                    }
                }
                
                _draggedObject->stopAllActions();
                _draggedObject->getParent()->reorderChild(_draggedObject, 100);
                
                _startDraggingCorrectObjectCallback(_draggedObject);
//                auto it = _startDraggingConcreteObjectCallbacks.find(dynamic_cast<ToyScenarioObject*>(_draggedObject)->scenarioObjectName);
//                if(it != _startDraggingConcreteObjectCallbacks.end()){
//                    it->second(_draggedObject);
//                }
                
                _currentDraggingState = Dragging;
                _draggingWrongObject = false;
                
                return true;
            }
        
        }
        
        // if no object found
        cocos2d::Node* wrongObj = NULL;
        
        for(int i = 0; i < _wrongDraggedObjects.size(); ++i){ //todo use extended grab area?
        
            if(!_allowDraggingInvisibleObjects && _wrongDraggedObjects[i]->getOpacity() == 0){
                continue;
            }
        
            if(ToyGeometryUtils::getBoundingBoxToWorld(_wrongDraggedObjects[i]).containsPoint(touch->getLocation())){
                if(wrongObj == nullptr || wrongObj->getLocalZOrder() < _wrongDraggedObjects[i]->getLocalZOrder()){
                    wrongObj = _wrongDraggedObjects[i];
                }
            }
        }
    
        if(wrongObj){
            _startDraggingWrongObjectCallback(wrongObj);

            if(_allowDraggingWrongObjects){
                _draggedObject = wrongObj;
                _draggedObjectOriginalZ = _draggedObject->getLocalZOrder();
                _startLocation = touch->getLocation();
                _startingObjectPosition = _draggedObject->getPosition();
//                auto it = _startDraggingConcreteObjectCallbacks.find(dynamic_cast<ToyScenarioObject*>(_draggedObject)->scenarioObjectName);
//                if(it != _startDraggingConcreteObjectCallbacks.end()){
//                    it->second(_draggedObject);
//                }
                
                
                for(int i = ToySpriteAnimator::RF_ANIMATION_TAG_RANGE_MIN; i <= ToySpriteAnimator::RF_ANIMATION_TAG_RANGE_MAX; ++i){
                    auto action = _draggedObject->getActionByTag(i);
                    if(action){
                        _cachedDraggedObjectRFActionTags.push_back(i);
                    }
                }
                
                _draggedObject->stopAllActions();
                _draggedObject->getParent()->reorderChild(_draggedObject, 100);
                
                _currentDraggingState = Dragging;
                _draggingWrongObject = true;
                
                return true;
            }
        }
        
        return false;
    };
}

TouchHandlerFunction ToyDragAndDropHandler::prepareTouchMovedHandler()
{    
    return [&](cocos2d::Touch* touch, cocos2d::Event* event){
        
        if(_draggedObject){
        
            auto dx = touch->getLocation().x - _startLocation.x;
            auto dy = touch->getLocation().y - _startLocation.y;
            auto newPosition = cocos2d::Point(_startingObjectPosition.x + dx, _startingObjectPosition.y + dy);
            _draggedObject->setPosition(newPosition);
//            printf("POSX: %f POSY: %f\n", (_draggedObject->getPositionX()-_draggedObject->getParent()->getBoundingBox().size.width/2)/1.33/*-_draggedObject->getBoundingBox().size.width/2*/, (_draggedObject->getPositionY() - _draggedObject->getParent()->getBoundingBox().size.height/2)/1.33);
            
            if(_moreAccurateDestinationCheck){
                
                bool isRightObject, isWrongObject;
                auto destinationObject = this->detectDestination(touch->getLocation(), isRightObject, isWrongObject);
                if(isRightObject){
                    this->onMovingOverCorrectDestination();
                }
                else if(isWrongObject){
                    this->onMovingOverWrongDestination(destinationObject);
                }
                else {
                    this->onMovingOverNothing();
                }
                
            } else if(this->checkDraggingOverCorrectDestination(touch->getLocation())){
                
                this->onMovingOverCorrectDestination();
                                     
            } else {
                
                auto wrongDstObj = this->checkDraggingOverWrongDestination(touch->getLocation());
                
                if(wrongDstObj){
                
                    this->onMovingOverWrongDestination(wrongDstObj);
                    
                } else {
                    
                    this->onMovingOverNothing();
                    
                }
            }
        }
    };
}
    
void ToyDragAndDropHandler::onMovingOverCorrectDestination()
{
    if(_currentDraggingState != DraggingOverCorrectDestination){
        _draggedObjectEnterCorrectDestinationCallback(_draggedObject, _destinationObject);
    }

    _currentDraggingState = DraggingOverCorrectDestination;
}
    
void ToyDragAndDropHandler::onMovingOverWrongDestination(cocos2d::Node* wrongDestinationObject)
{
    if((_currentDraggingState != DraggingOverWrongDestination || (_currentDraggingState == DraggingOverWrongDestination && _draggingOverWrongDestinationObject != wrongDestinationObject))) {
         _draggedObjectEnterWrongDestinationCallback(_draggedObject, wrongDestinationObject);
    }
                    
    _currentDraggingState = DraggingOverWrongDestination;
    _draggingOverWrongDestinationObject = wrongDestinationObject;
}
      
void ToyDragAndDropHandler::onMovingOverNothing()
{
    _currentDraggingState = Dragging;
    _draggingOverWrongDestinationObject = NULL;
}
    
TouchHandlerFunction ToyDragAndDropHandler::prepareTouchEndedHandler() //todo tu dokonczyc case kiedy dragujemy zly obiekt
{
    return [&](cocos2d::Touch* touch, cocos2d::Event* event){
            
        if(_draggedObject){
        
             if(_moreAccurateDestinationCheck){
                bool isRightObject, isWrongObject;
                auto destinationObject = this->detectDestination(touch->getLocation(), isRightObject, isWrongObject);
                
                if(isRightObject){
                    this->onTouchEndedOverCorrectDestination();
                }
                else if(isWrongObject){
                    _wrongDestinationObjectCallback(destinationObject);
                    this->draggedObjectMoveBack(true);
                }
                else {
                    _dropTooSoonCallback();
                    this->draggedObjectMoveBack(true);
                }
                
            } else if(this->checkDraggingOverCorrectDestination(touch->getLocation())){
                 
                 this->onTouchEndedOverCorrectDestination();
            
            } else {
                
                this->draggedObjectMoveBack(true);
                auto wrongDstObj = this->checkDraggingOverWrongDestination(touch->getLocation());
                
                if(wrongDstObj){
                    _wrongDestinationObjectCallback(wrongDstObj);
                } else {
                    _dropTooSoonCallback();
                }
            }
            _draggedObject = NULL;
        }
        
        _currentDraggingState = NotDragging;
    };
}
    
void ToyDragAndDropHandler::onTouchEndedOverCorrectDestination()
{
     if(_draggingWrongObject){
     
        _draggedWrongObjectCallback(_draggedObject);
        this->draggedObjectMoveBack(true);
        
     } else {

        _objectDragNDropSuccessBehaviour(_destinationObject, _draggedObject, _startingObjectPosition);
         
        if(_allowMultipleDrag){
            this->draggedObjectMoveBack(true);
        } else {
            auto iter = std::find(_objectsToDrag.begin(), _objectsToDrag.end(), _draggedObject);
            _objectsToDrag.erase(iter);
        }
        
        if(_objectsToDrag.size() > 0){
            _successCallback(_destinationObject, _draggedObject);
        } else {
            _finalSuccessCallback(_destinationObject, _draggedObject);
        }
    }
    
    _draggedObject = NULL;
}
    
cocos2d::Node* ToyDragAndDropHandler::detectDestination(const cocos2d::Point& touchLocation, bool& isRightObject, bool& isWrongObject)
{
    auto rightDestinationIntersection = ToyGeometryUtils::calculateIntersectionPercentage(ToyGeometryUtils::getBoundingBoxToWorld(_draggedObject), ToyGeometryUtils::getBoundingBoxToWorld(_destinationObject));
    
    float greatestWrongObjectIntersection = 0;
    cocos2d::Node* bestIntersectingWrongObject = NULL;
    
    for(int i = 0; i < _wrongDestinationObjects.size(); ++i){
           
        auto wrongObjectIntersection = ToyGeometryUtils::calculateIntersectionPercentage(ToyGeometryUtils::getBoundingBoxToWorld(_draggedObject), ToyGeometryUtils::getBoundingBoxToWorld(_wrongDestinationObjects[i]), 1);
            
        if(wrongObjectIntersection > greatestWrongObjectIntersection){
            greatestWrongObjectIntersection = wrongObjectIntersection;
            bestIntersectingWrongObject = _wrongDestinationObjects[i];
        }
    }
    
    cocos2d::Node* destinationObject = NULL;
    isRightObject = false;
    isWrongObject = false;
    
    if(rightDestinationIntersection >= greatestWrongObjectIntersection && rightDestinationIntersection > 0){
        destinationObject = _destinationObject;
        isRightObject = true;
    } else if(greatestWrongObjectIntersection != 0){
        destinationObject = bestIntersectingWrongObject;
        isWrongObject = true;
    }
            
    return destinationObject;
}
    
bool ToyDragAndDropHandler::checkDraggingOverCorrectDestination(const cocos2d::Point& touchLocation)
{
    return _draggedObject != NULL && ((!_fingerOnlyDestinationCheck && ToyGeometryUtils::getBoundingBoxToWorld(_destinationObject).intersectsRect(ToyGeometryUtils::getBoundingBoxToWorld(_draggedObject))) || (_fingerOnlyDestinationCheck && ToyGeometryUtils::getBoundingBoxToWorld(_destinationObject).containsPoint(touchLocation)));
}
    
cocos2d::Node* ToyDragAndDropHandler::checkDraggingOverWrongDestination(const cocos2d::Point& touchLocation)
{
    if(!_draggedObject){
    
        return NULL;
        
    } else {
    
        cocos2d::Node* wrongDstObj = nullptr;
        
        for(int i = 0; i < _wrongDestinationObjects.size(); ++i){
            
            if(ToyGeometryUtils::getBoundingBoxToWorld(_wrongDestinationObjects[i]).intersectsRect(ToyGeometryUtils::getBoundingBoxToWorld(_draggedObject))){
                wrongDstObj = _wrongDestinationObjects[i];
                break;
            }
        }
        
        return wrongDstObj;
    }
}
    
void ToyDragAndDropHandler::draggedObjectMoveBack(bool restoreActions)
{
    if(_draggedObject){
        _draggedObject->runAction(cocos2d::Sequence::create(cocos2d::MoveTo::create(0.2, _startingObjectPosition),
                                                            cocos2d::CallFunc::create(std::bind([&](cocos2d::Node* object, int zOrder){
            object->getParent()->reorderChild(object, zOrder);
//            printf("z order: %d", zOrder);
        }, _draggedObject, _draggedObjectOriginalZ)), nullptr));
        
        if(restoreActions){
            for(int i = 0; i < _cachedDraggedObjectRFActionTags.size(); ++i){
                int actionTag = _cachedDraggedObjectRFActionTags[i];
                ToySpriteAnimator::restoreRFAction(_draggedObject, actionTag);
            }
        }
        
        _cachedDraggedObjectRFActionTags.clear();
    }
}