// // HLayoutParser.cpp // SteveMaggieCpp // // Created by Katarzyna Kalinowska-Górska on 16.05.2017. // // #include #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(); 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(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(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; }