// // LayoutParser.cpp // SteveMaggieCpp // // Created by Katarzyna Kalinowska-Górska on 16.05.2017. // // #include #include "LayoutParser.h" #include "LayoutObject.h" #include "ScalingUtils.h" #include "StringUtils.h" #include "JSONParseUtils.h" #include "PlainNode.h" #include "PlainSprite.h" #include "SimpleButton.h" #include "LevelPickerView.h" #include "TwoStateButton.h" #include "ResourceUtilities.h" #include "ChangingSprite.h" #include "PlainLabel.h" #include "ContainerSprite.h" #include "ProgressSliderNode.h" class LayoutObjectMapper { public: LayoutObject* createObjectFromClassName(std::string className, const rapidjson::Value& nodeData, std::string resFolder, std::string altResFolder) { LayoutObject* newObject = NULL; //TODO TUTAJ BRAC POD UWAGE ADDITIONANODE DATA moze wziac layoutparsera convenient metode std::string resourcesPath = resFolder; if(JSONParseUtils::checkMemberBool(nodeData, "useAlternativePath", true)) { resourcesPath = altResFolder; } // resourcesPath = ResourceUtilities::getInstance().getFullPathForDownloadedFile(resourcesPath); //TODO moze to gdzies wyrzucic jednak albo chociaz dac parameter czy to robic? if(className == "PlainNode"){ newObject = PlainNode::create(); } else if(className == "ColourLayer"){ auto colour = JSONParseUtils::getColor3B(nodeData["colour"]); cocos2d::Color4B col = cocos2d::Color4B(colour,255); newObject = PlainNode::createWithColour(col); } else if(className == "Label"){ const std::string& text = nodeData["text"].GetString(); const std::string& fontPath = nodeData["fontPath"].GetString(); int fontSize = nodeData["baseFontSize"].GetInt(); newObject = PlainLabel::create(text, fontPath, fontSize); } else if(className == "ChangingSprite") { std::string filePath1 = resourcesPath + nodeData["imagePath1"].GetString(); std::string filePath2 = resourcesPath + nodeData["imagePath2"].GetString(); newObject = ChangingSprite::createWithSpritePaths(filePath1, filePath2); } else if(className == "ContainerSprite") { std::string filePath = resourcesPath + nodeData["imagePath"].GetString(); newObject = ContainerSprite::createWithContainerSpritePath(filePath); } // else if(className == "ColourableSprite"){ // // std::string filePath1 = resourcesPath + nodeData["imagePath1"].GetString(); // std::string filePath2 = resourcesPath + nodeData["imagePath2"].GetString(); // newObject = ColourableSprite::createWithSpritePaths(filePath1, filePath2); // } else if(className == "PlainSprite"){ std::string filePath = resourcesPath + nodeData["imagePath"].GetString(); newObject = PlainSprite::createWithSpritePath(filePath); } else if(className == "SimpleButton"){ newObject = SimpleButton::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() ? LevelView::create(path) : LevelView::create(path, levelName); } else if (className == "LevelPickerView"){ auto levelImagePaths = JSONParseUtils::parseStringArray(nodeData["levelImagePaths"]); auto levelNames = nodeData.HasMember("levelNames") ? JSONParseUtils::parseStringArray(nodeData["levelNames"]) : std::vector(); for(auto& path : levelImagePaths){ path = resourcesPath + path; } newObject = LevelPickerView::create(levelImagePaths, levelNames); } else if(className == "TwoStateButton"){ std::string inactiveImagePath = resourcesPath + nodeData["inactiveImagePath"].GetString(); std::string activeImagePath = resourcesPath + nodeData["activeImagePath"].GetString(); newObject = TwoStateButton::create(inactiveImagePath, activeImagePath); } else if(className == "ProgressSliderNode"){ std::string bgFileName = nodeData["bgImagePath"].GetString(); std::string bgFilePath = bgFileName == "" ? bgFileName : resourcesPath + bgFileName; std::string thumbFilePath = resourcesPath + nodeData["thumbImagePath"].GetString(); int min = nodeData["min"].GetInt(); int max = nodeData["max"].GetInt(); std::string orientation = nodeData["orientation"].GetString(); int orientationInt = orientation == "vertical" ? ProgressSliderNodeOrientationVertical : ProgressSliderNodeOrientationHorizontal; newObject = ProgressSliderNode::create(bgFilePath, thumbFilePath, min, max, orientationInt); } else if(className == "RepeatSprite"){ std::string filePath = resourcesPath + nodeData["imagePath"].GetString(); auto texture = cocos2d::Director::getInstance()->getTextureCache()->addImage(filePath); newObject = PlainSprite::createWithTexture(texture); cocos2d::Texture2D::TexParams params; params.sAddressMode = cocos2d::backend::SamplerAddressMode::REPEAT; //GL_REPEAT; params.tAddressMode = cocos2d::backend::SamplerAddressMode::REPEAT; dynamic_cast(newObject)->getTexture()->setTexParameters(params); // newObject->setContentSize(_shelf->getBoundingBox().size); } else // create a plain sprite { std::string filePath = resourcesPath + nodeData["imagePath"].GetString(); newObject = PlainSprite::createWithSpritePath(filePath); } return newObject; } }; void LayoutParser::loadLayoutFromJSONFile(const std::string& jsonFilePath, LayoutViewInterface* scene) { std::string jsonString = JSONParseUtils::loadJSONFromFile(jsonFilePath); return this->loadLayoutFromJSONString(jsonString, scene); } void LayoutParser::loadLayoutFromDownloadedJSONFile(const std::string& jsonFilePath, LayoutViewInterface* scene) { // std::string downloadedFilePath = ResourceUtilities::getInstance().getFullPathForDownloadedFile(jsonFilePath, true); std::string jsonString = cocos2d::FileUtils::getInstance()->getStringFromFile(jsonFilePath); //this->loadJSONFromFile(downloadedFilePath + jsonFilePath); return this->loadLayoutFromJSONString(jsonString, scene); } void LayoutParser::loadLayoutFromJSONString(const std::string& jsonString, LayoutViewInterface* 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; _savedBgStretchScale = 1.f; if(document.HasMember("backgroundMusicPath")){ std::string bgMusicFilePath = document["backgroundMusicPath"].GetString(); bgMusicFilePath = ResourceUtilities::getInstance().getFullPathForDownloadedFile(bgMusicFilePath, false); scene->setupBackgroundMusic(bgMusicFilePath); } const auto& sceneLayoutJSON = document["sceneLayout"]; const auto& layersJSON = sceneLayoutJSON["layers"].GetArray(); for(const auto& layerJSON : layersJSON) { 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(); } } 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 = ResourceUtilities::getInstance().getFullPathForDownloadedFile(resFolder); altResFolder = ResourceUtilities::getInstance().getFullPathForDownloadedFile(altResFolder); rapidjson::Value layerJSONCopy(layerJSON, this->_parsedDocument->GetAllocator()); this->parseChildObjects(scene, layerJSONCopy, newLayer, objectLayoutsFolder, resFolder, altResFolder, true); } this->_parsedDocument = NULL; } void LayoutParser::parseChildObjects(LayoutViewInterface* 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* LayoutParser::parseObject(LayoutViewInterface* 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; if(nodeData.HasMember("loadObjectFromFile")){ std::string objectDataPath = objectLayoutsFolder + nodeData["loadObjectFromFile"].GetString(); objectDataPath = ResourceUtilities::getInstance().getFullPathForDownloadedFile(objectDataPath, true); std::string jsonString = JSONParseUtils::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); } } } LayoutObjectMapper mapper; std::string type = "PlainSprite"; auto typeValue = this->getValueForKey("type", nodeData); if(typeValue != NULL){ type = typeValue->GetString(); } auto newObjectAsLayoutObject = mapper.createObjectFromClassName(type, nodeData, resFolder, altResFolder); if(newObjectAsLayoutObject == NULL){ return NULL; } newObjectAsLayoutObject->loadPropertiesFromJSON(nodeData, scene, resFolder, altResFolder); newObjectAsLayoutObject->prepareSize(nodeData, newObjectWidth, newObjectHeight); newObject = dynamic_cast(newObjectAsLayoutObject); if(newObjectAsLayoutObject->isWidget()){ newObjectAsLayoutObject->setOnTouchBeganCallback(CC_CALLBACK_2(LayoutViewInterface::touchHandlerForWidget, scene)); newObjectAsLayoutObject->setOnTouchEndedCallback(CC_CALLBACK_2(LayoutViewInterface::touchHandlerForWidget, scene)); newObjectAsLayoutObject->setOnTouchCancelledCallback(CC_CALLBACK_2(LayoutViewInterface::touchHandlerForWidget, scene)); } // float objectScale = 1; // auto scaleFromJSON = this->getValueForKey("scale", nodeData, additionalObjectJsonData); // if(scaleFromJSON != NULL){ // objectScale = scaleFromJSON->GetFloat(); // } std::string 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/ScalingUtils::getAdjustedContentScaleFactor(); float additionalPaddingW = isTopLayer /*&& !ignoreScaling */? ScalingUtils::getInstance().getScaledScreenSurplusWidth()*scaleToDesignSize / 2 : 0; float additionalPaddingH = isTopLayer /*&& !ignoreScaling */? ScalingUtils::getInstance().getScaledScreenSurplusHeight()*scaleToDesignSize / 2 : 0; // float scaleToDesignSize = ScalingUtils::scaleAspectFillToDesignIpadProSize(); auto objectScale = scaleToDesignSize;//scale;//(ignoreScaling ? 1 : scale)/scaleToDesignSize; auto scaleAsBackground = this->getValueForKey("scaleAsBackgroundStretch", nodeData); if(scaleAsBackground != nullptr && scaleAsBackground->GetBool() == true){ // objectScale = cocos2d::Director::getInstance()->getContentScaleFactor() / _savedBgStretchScale; newObject->setScale(_savedBgStretchScale); objectScale = scaleToDesignSize*_savedBgStretchScale; } //TODO THIS IS NOT OK THAT WE HAVE THE SAME THING HERE AND IN THE SIMPLEBUTTON OBJECTS, FIX if(ScalingUtils::isSmallDevice()){ if((ScalingUtils::isElementTooSmallForSmallDevice(newObjectWidth) && (type == "TwoStateButton" || type == "SimpleButton")) || (JSONParseUtils::hasMemberBool(nodeData, "scaleUpForSmallDevices") && nodeData["scaleUpForSmallDevices"].GetBool() == true)){ objectScale *= ScalingUtils::getScaleForSmallDevice(); } } // if(ScalingUtils::isSmallDevice() && (type == "TwoStateButton" || type == "SimpleButton")){//TODO ugly, should probably be somewhere in the simplebutton class or somewhere // objectScale = objectScale * ScalingUtils::getScaleForSmallDevice(); // } // if(ScalingUtils::isSmallDevice() && ScalingUtils::isElementTooSmallForSmallDevice(newObject->getBoundingBox().size.width)){// && (type == "TwoStateButton" || type == "SimpleButton")){//TODO ugly, should probably be somewhere in the simplebutton class or somewhere // objectScale = objectScale * ScalingUtils::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_w") != std::string::npos){ //object needs an 0.5 anchor point auto tokens = StringUtils::splitString(xPlacementMode, ' '); float padding = 0; float width = atof(tokens[1].c_str())*ScalingUtils::widthScale(); float place = atof(tokens[3].c_str()); if(xPlacementMode.find("padding") != std::string::npos){ padding = atof(tokens[5].c_str())*objectScale; } objectXPos = width*(place-1+0.5)+place*padding; } else if(xPlacementMode.find("divide") != std::string::npos){ //object needs an 0.5 anchor point auto tokens = StringUtils::splitString(xPlacementMode, ' '); float divide = atof(tokens[1].c_str()); if(xPlacementMode.find("place") != std::string::npos){ 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_h") != std::string::npos){ //object needs an 0.5 anchor point auto tokens = StringUtils::splitString(yPlacementMode, ' '); float padding = 0; float height = atof(tokens[1].c_str())*ScalingUtils::heightScale(); float place = atof(tokens[3].c_str()); if(yPlacementMode.find("padding") != std::string::npos){ padding = atof(tokens[5].c_str())*objectScale; } objectYPos = height*(place-1+0.5)+place*padding; } else if(yPlacementMode.find("divide") != std::string::npos){ auto tokens = StringUtils::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); newObjectAsLayoutObject->originalPosition = newObject->getPosition(); auto stretchMode = this->getValueForKey("stretchMode", nodeData); std::string aspectFill = "aspectFill"; std::string aspectFit = "aspectFit"; std::string scaleToFill = "scaleToFill"; if(stretchMode != NULL){ float stretchScale = 1.f; if(aspectFill == stretchMode->GetString()){ stretchScale = ScalingUtils::imageAspectFillGetScale(cocos2d::Size(newObjectWidth, newObjectHeight), cocos2d::Size(parentNodeWidth, parentNodeHeight)); // newObject->setScale(stretchScale); if(JSONParseUtils::hasMemberFloat(nodeData, "thresholdAspectRatio")){ auto threshold = nodeData["thresholdAspectRatio"].GetFloat(); if(ScalingUtils::getDeviceAspectRatio() > threshold){ auto adjustedParentWidth = parentNodeHeight*threshold; stretchScale = ScalingUtils::imageAspectFillGetScale(cocos2d::Size(newObjectWidth, newObjectHeight), cocos2d::Size(adjustedParentWidth, parentNodeHeight)); } } newObject->setContentSize(cocos2d::Size(newObjectWidth*stretchScale, newObjectHeight*stretchScale)); } else if(aspectFit == stretchMode->GetString()){ stretchScale = ScalingUtils::imageAspectFitGetScale(cocos2d::Size(newObjectWidth, newObjectHeight), cocos2d::Size(parentNodeWidth, parentNodeHeight)); newObject->setContentSize(cocos2d::Size(newObjectWidth*stretchScale, newObjectHeight*stretchScale)); // newObject->setScale(stretchScale); } else if(scaleToFill == stretchMode->GetString()){ stretchScale = parentNodeWidth/newObjectWidth; newObject->setContentSize(cocos2d::Size(parentNodeWidth, parentNodeHeight)); // newObject->setScaleX(parentNodeWidth/newObjectWidth); // newObject->setScaleY(parentNodeHeight/newObjectHeight); } if(objectName == "background"){ _savedBgStretchScale = 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* LayoutParser::getValueForKey(std::string key, const rapidjson::Value& mainNodeData) { if(mainNodeData.HasMember(key.c_str())){ return &mainNodeData[key.c_str()]; } return NULL; }