HLayoutParser.cpp 15.7 KB
//
//  HLayoutParser.cpp
//  SteveMaggieCpp
//
//  Created by Katarzyna Kalinowska-Górska on 16.05.2017.
//
//

#include <stdio.h>
#include "HLayoutParser.h"
#include "HLayoutObject.h"
#include "HScalingUtils.h"
#include "HStringUtils.h"
#include "HJSONParseUtils.h"

#include "HPlainNode.h"
#include "HPlainSprite.h"
#include "HalloweenSimpleButton.h"
#include "HLevelPickerView.h"
#include "HTwoStateButton.h"
#include "HResourceUtilities.h"

class HLayoutObjectMapper
{
    public:
        HLayoutObject* createObjectFromClassName(std::string className, const rapidjson::Value& nodeData, std::string resFolder, std::string altResFolder)
        {
            HLayoutObject* newObject = NULL; //TODO TUTAJ BRAC POD UWAGE ADDITIONANODE DATA moze wziac HLayoutParsera convenient metode
            
            std::string resourcesPath = resFolder;
            if(HJSONParseUtils::checkMemberBool(nodeData, "useAlternativePath", true))
            {
                    resourcesPath = altResFolder;
            }
            
//            resourcesPath = HResourceUtilities::getInstance().getFullPathForDownloadedFile(resourcesPath); //TODO moze to gdzies wyrzucic jednak albo chociaz dac parameter czy to robic?
            
           if(className == "PlainNode"){
                newObject = HPlainNode::create();
            }
            else if(className == "PlainSprite"){
                std::string filePath = resourcesPath + nodeData["imagePath"].GetString();
                newObject = HPlainSprite::createWithSpritePath(filePath);
            }
            else if(className == "SimpleButton"){
                newObject = HalloweenSimpleButton::create();
            } else if (className == "LevelView"){
                auto levelImagePath = nodeData["levelImagePath"].GetString();
                std::string levelName = nodeData.HasMember("levelName") ? nodeData["levelName"].GetString() : "";
                auto path = resourcesPath + levelImagePath;
                newObject = levelName.empty() ? HLevelView::create(path) : HLevelView::create(path, levelName);
            }
            else if (className == "LevelPickerView"){
                auto levelImagePaths = HJSONParseUtils::parseStringArray(nodeData["levelImagePaths"]);
                auto levelNames = nodeData.HasMember("levelNames") ? HJSONParseUtils::parseStringArray(nodeData["levelNames"]) : std::vector<std::string>();
                for(auto& path : levelImagePaths){
                    path = resourcesPath + path;
                }
                newObject = HLevelPickerView::create(levelImagePaths, levelNames);
            }
            else if(className == "TwoStateButton"){
                std::string inactiveImagePath = resourcesPath + nodeData["inactiveImagePath"].GetString();
                std::string activeImagePath = resourcesPath + nodeData["activeImagePath"].GetString();
                newObject = HTwoStateButton::create(inactiveImagePath, activeImagePath);
            }
            else // create a plain sprite
            {
                std::string filePath = resourcesPath + nodeData["imagePath"].GetString();
                newObject = HPlainSprite::createWithSpritePath(filePath);
            }

            
            return newObject;
        }
};

std::string HLayoutParser::loadJSONFromFile(const std::string& jsonFilePath)
{
    std::string fullPath = cocos2d::FileUtils::getInstance()->fullPathForFilename(jsonFilePath);
    return cocos2d::FileUtils::getInstance()->getStringFromFile(fullPath);
}

void HLayoutParser::loadLayoutFromJSONFile(const std::string& jsonFilePath, HLayoutViewInterface* scene)
{
    std::string jsonString = this->loadJSONFromFile(jsonFilePath);
    return this->loadLayoutFromJSONString(jsonString, scene);
}

void HLayoutParser::loadLayoutFromDownloadedJSONFile(const std::string& jsonFilePath, HLayoutViewInterface* scene)
{
//    std::string downloadedFilePath = HResourceUtilities::getInstance().getFullPathForDownloadedFile(jsonFilePath, true);
    std::string jsonString = cocos2d::FileUtils::getInstance()->getStringFromFile(jsonFilePath); //this->loadJSONFromFile(downloadedFilePath + jsonFilePath);
    return this->loadLayoutFromJSONString(jsonString, scene);
}

void HLayoutParser::loadLayoutFromJSONString(const std::string& jsonString, HLayoutViewInterface* scene) //TODO: invalid layout file will cause a crash, will have to fix it!!!
{
    rapidjson::Document document;
    document.Parse(jsonString.c_str());
    this->_parsedDocument = &document;
    
    if(document.HasMember("backgroundMusicPath")){
        std::string bgMusicFilePath = document["backgroundMusicPath"].GetString();
        bgMusicFilePath = HResourceUtilities::getInstance().getFullPathForDownloadedFile(bgMusicFilePath, false);
        scene->setupBackgroundMusic(bgMusicFilePath);
    }
    const auto& sceneLayoutJSON = document["sceneLayout"];
    const auto& layersJSON = sceneLayoutJSON["layers"].GetArray();
    
    for(const auto& layerJSON : layersJSON)
    {
        cocos2d::Layer* newLayer = NULL;
        
        if(layerJSON.HasMember("color")){
        
            const auto& color = layerJSON["color"];
            auto colorR = color["r"].GetInt();
            auto colorG = color["g"].GetInt();
            auto colorB = color["b"].GetInt();
            auto colorA = color["a"].GetInt();
            newLayer = cocos2d::LayerColor::create(cocos2d::Color4B(colorR, colorG, colorB, colorA));
            
        } else {
        
            newLayer = cocos2d::Layer::create();
            
        }
        
        auto sceneAsNode = dynamic_cast<cocos2d::Node*>(scene);
        sceneAsNode->addChild(newLayer);
        scene->addLayer(newLayer);
        
        if(layerJSON.HasMember("name") && layerJSON["name"].IsString()){
            auto layerName = std::string(layerJSON["name"].GetString());
            scene->addObject(layerName, newLayer);
        }
        
        std::string objectLayoutsFolder = "";
        std::string resFolder = "";
        std::string altResFolder = "";
        
        if(document.HasMember("resFolder")){
        
            const auto& resFolderJSON = document["resFolder"];
            
            if(resFolderJSON.HasMember("objectLayoutsPath")){
               objectLayoutsFolder = resFolderJSON["objectLayoutsPath"].GetString();
            }
            
            if(resFolderJSON.HasMember("path")){
               resFolder = resFolderJSON["path"].GetString();
            }
            
            if(resFolderJSON.HasMember("alternativePath")){
               altResFolder = resFolderJSON["alternativePath"].GetString();
            }
        }
        
        resFolder = HResourceUtilities::getInstance().getFullPathForDownloadedFile(resFolder);
        altResFolder = HResourceUtilities::getInstance().getFullPathForDownloadedFile(altResFolder);
        
        rapidjson::Value layerJSONCopy(layerJSON, this->_parsedDocument->GetAllocator());
        this->parseChildObjects(scene, layerJSONCopy, newLayer, objectLayoutsFolder, resFolder, altResFolder, true);
    }
    
    this->_parsedDocument = NULL;
}

void HLayoutParser::parseChildObjects(HLayoutViewInterface* scene, rapidjson::Value& nodeData, cocos2d::Node* createdNode, std::string objectLayoutsFolder, std::string resFolder, std::string altResFolder, bool isTopLayer)
{
   if(nodeData.HasMember("objects")){
        auto objects = nodeData["objects"].GetArray();
        for(const auto& objectJSON : objects){
            rapidjson::Value objectJSONCopy(objectJSON, this->_parsedDocument->GetAllocator());
            auto newObject = this->parseObject(scene, objectJSONCopy, createdNode, objectLayoutsFolder, resFolder, altResFolder, isTopLayer);
            if(newObject != NULL){
                this->parseChildObjects(scene, objectJSONCopy, newObject, objectLayoutsFolder, resFolder, altResFolder, false);
            }
        }
   }
}

cocos2d::Node* HLayoutParser::parseObject(HLayoutViewInterface* scene, rapidjson::Value& nodeData, cocos2d::Node* parentNode, std::string objectLayoutsFolder, std::string resFolder, std::string altResFolder, bool isTopLayer)
{
    //todo memory leaks?
    cocos2d::Node* newObject = NULL;
    float newObjectWidth, newObjectHeight;
//    auto scale = cocos2d::Director::getInstance()->getContentScaleFactor();
    
    if(nodeData.HasMember("loadObjectFromFile")){
        std::string objectDataPath = objectLayoutsFolder + nodeData["loadObjectFromFile"].GetString();
        objectDataPath = HResourceUtilities::getInstance().getFullPathForDownloadedFile(objectDataPath, true);
        std::string jsonString = this->loadJSONFromFile(objectDataPath);
        
        if(jsonString != ""){
            rapidjson::Document additionalObjectJSON;
            additionalObjectJSON.Parse(jsonString.c_str());
            const auto& additionalObjectJsonData = additionalObjectJSON["object"];

            auto& allocator = this->_parsedDocument->GetAllocator();

            for (rapidjson::Value::ConstMemberIterator itr = additionalObjectJsonData.MemberBegin(); itr != additionalObjectJsonData.MemberEnd(); ++itr)
            {
                const char* memberName = itr->name.GetString();
                const rapidjson::Value& memberValue = itr->value;
                nodeData.AddMember(rapidjson::Value(memberName, allocator).Move(), rapidjson::Value(memberValue, allocator).Move(), allocator);
            }
        }
    }
    
    HLayoutObjectMapper mapper;
    
    std::string type = "PlainSprite";
    auto typeValue = this->getValueForKey("type", nodeData);
    if(typeValue != NULL){
        type = typeValue->GetString();
    }
    
    auto newObjectAsHLayoutObject = mapper.createObjectFromClassName(type, nodeData, resFolder, altResFolder);
    
    if(newObjectAsHLayoutObject == NULL){
        return NULL;
    }
    
    newObjectAsHLayoutObject->loadPropertiesFromJSON(nodeData, scene, resFolder, altResFolder);
    newObjectAsHLayoutObject->prepareSize(nodeData, newObjectWidth, newObjectHeight);
    newObject = dynamic_cast<cocos2d::Node*>(newObjectAsHLayoutObject);
    
    if(newObjectAsHLayoutObject->isWidget()){
        newObjectAsHLayoutObject->setOnTouchBeganCallback(CC_CALLBACK_2(HLayoutViewInterface::touchHandlerForWidget, scene));
        newObjectAsHLayoutObject->setOnTouchEndedCallback(CC_CALLBACK_2(HLayoutViewInterface::touchHandlerForWidget, scene));
        newObjectAsHLayoutObject->setOnTouchCancelledCallback(CC_CALLBACK_2(HLayoutViewInterface::touchHandlerForWidget, scene));
    }
    
//    float objectScale = 1;
//    auto scaleFromJSON = this->getValueForKey("scale", nodeData, additionalObjectJsonData);
//    if(scaleFromJSON != NULL){
//        objectScale = scaleFromJSON->GetFloat();
//    }
    
    auto objectName = this->getValueForKey("name", nodeData)->GetString();
//    scene->_objects.insert({objectName, newObject});
    scene->addObject(objectName, newObject);
    
    // object placement; maybe put into a separate function
//    auto ignoreScalingJSON = this->getValueForKey("ignore_scaling", nodeData);
//    bool ignoreScaling = false;
//    if(ignoreScalingJSON != NULL){
//        ignoreScaling = ignoreScalingJSON->GetBool(); //TODO ignore scaling is int
//    }
    
    float scaleToDesignSize = 1/cocos2d::Director::getInstance()->getContentScaleFactor();
    
    float additionalPaddingW = isTopLayer /*&& !ignoreScaling */? HScalingUtils::getInstance().getScaledScreenSurplusWidth()*scaleToDesignSize / 2 : 0;
    float additionalPaddingH = isTopLayer /*&& !ignoreScaling */? HScalingUtils::getInstance().getScaledScreenSurplusHeight()*scaleToDesignSize / 2 : 0;
    
//    float scaleToDesignSize = HScalingUtils::scaleAspectFillToDesignIpadProSize();
    auto objectScale = scaleToDesignSize;//scale;//(ignoreScaling ? 1 : scale)/scaleToDesignSize;
    if(HScalingUtils::isSmallDevice() && (type == "TwoStateButton" || type == "SimpleButton")){//TODO ugly, should probably be somewhere in the HalloweenSimpleButton class or somewhere
        objectScale = objectScale * HScalingUtils::getScaleForSmallDevice();
    }
    
    float objectXPos = 0, objectYPos = 0;
    bool skipPlacement = false;
    
    auto parentNodeWidth = parentNode->getContentSize().width;
    auto parentNodeHeight = parentNode->getContentSize().height;
    
    auto layoutMode = this->getValueForKey("layoutMode", nodeData);
    std::string fillParentMode = "fillParent"; //TODO enum
    if(layoutMode != NULL && fillParentMode == layoutMode->GetString()){
        objectXPos = parentNodeWidth / 2; //TODO override getContentSize() everywhere
        objectYPos = parentNodeHeight / 2;
        newObject->setContentSize(cocos2d::Size(parentNodeWidth, parentNodeHeight));
        skipPlacement = true;
    }
    
    if(!skipPlacement){
    
        const auto& xPosJSON = this->getValueForKey("centerXPos", nodeData);
        std::string xPlacementMode = this->getValueForKey("value", *xPosJSON)->GetString();
        if(xPlacementMode == "center"){
            objectXPos = parentNodeWidth/2;
        } else if(xPlacementMode == "left"){
            objectXPos = newObjectWidth/2 + additionalPaddingW;
        } else if(xPlacementMode == "right"){
            objectXPos = parentNodeWidth - newObjectWidth/2 - additionalPaddingW;
        } else if(xPlacementMode.find("divide") != std::string::npos){
            auto tokens = HStringUtils::splitString(xPlacementMode, ' ');
            float divide = atof(tokens[1].c_str());
            float place = atof(tokens[3].c_str());
            objectXPos = parentNodeWidth*place/divide;
        }
        
        auto xPadding = this->getValueForKey("padding", *xPosJSON);
        if(xPadding != NULL){
            objectXPos += xPadding->GetFloat()*objectScale;
        }
        
        const auto& yPosJSON = this->getValueForKey("centerYPos", nodeData);
        std::string yPlacementMode = this->getValueForKey("value", *yPosJSON)->GetString();
        if(yPlacementMode == "center"){
            objectYPos = parentNodeHeight/2;
        } else if(yPlacementMode == "bottom"){
            objectYPos = newObjectHeight/2 + additionalPaddingH;
        } else if(yPlacementMode == "top"){
            objectYPos = parentNodeHeight - newObjectHeight/2 - additionalPaddingH;
        } else if(yPlacementMode.find("divide") != std::string::npos){
            auto tokens = HStringUtils::splitString(yPlacementMode, ' ');
            float divide = atof(tokens[1].c_str());
            float place = atof(tokens[3].c_str());
            objectYPos = parentNodeHeight*place/divide;
        }
        
        auto yPadding = this->getValueForKey("padding", *yPosJSON);
        if(yPadding != NULL){
            objectYPos += yPadding->GetFloat()*objectScale;
        }
        
    }
    
//    newObject->setScale(objectScale);
    newObject->setPosition(objectXPos, objectYPos);
    
    auto stretchMode = this->getValueForKey("stretchMode", nodeData);
    std::string aspectFill = "aspectFill";
    if(stretchMode != NULL && aspectFill == stretchMode->GetString()){
        float stretchScale = HScalingUtils::imageAspectFillGetScale(cocos2d::Size(newObjectWidth, newObjectHeight), cocos2d::Size(parentNodeWidth, parentNodeHeight));
        newObject->setScale(stretchScale);
    }
    
    auto depthJSON = this->getValueForKey("depth", nodeData);
    if(depthJSON != NULL){
        float depth = depthJSON->GetInt();
        parentNode->addChild(newObject, depth);
    } else {
        parentNode->addChild(newObject);
    }
    
    auto opacityJSON = this->getValueForKey("opacity", nodeData);
    if (opacityJSON != NULL) {
        int opacity = opacityJSON->GetInt();
        newObject->setOpacity(opacity);
    }
    
    return newObject;
}

const rapidjson::Value* HLayoutParser::getValueForKey(std::string key, const rapidjson::Value& mainNodeData)
{
    if(mainNodeData.HasMember(key.c_str())){
        return &mainNodeData[key.c_str()];
    }
    
    return NULL;
}