// // AniLayoutParser.cpp // SteveMaggieCpp // // Created by Katarzyna Kalinowska-Górska on 16.05.2017. // // #include #include "AniLayoutParser.h" #include "AniLayoutObject.h" #include "AniScalingUtils.h" #include "AniStringUtils.h" #include "AniJSONParseUtils.h" #include "AniPlainNode.h" #include "AniPlainSprite.h" #include "AniSimpleButton.h" #include "AniLevelPickerView.h" #include "AniTwoStateButton.h" #include "AniResourceUtilities.h" #include "AniChangingSprite.h" #include "AniMapLayer.h" #include "AniPlainLabel.h" #include "AniContainerSprite.h" #include "AniProgressSliderNode.h" #include "AniSoundsRepo.h" class AniLayoutObjectMapper { public: AniLayoutObject* createObjectFromClassName(std::string className, const rapidjson::Value& nodeData, std::string resFolder, std::string altResFolder) { AniLayoutObject* newObject = NULL; //TODO TUTAJ BRAC POD UWAGE ADDITIONANODE DATA moze wziac AniLayoutParsera convenient metode std::string resourcesPath = resFolder; if(AniJSONParseUtils::checkMemberBool(nodeData, "useAlternativePath", true)) { resourcesPath = altResFolder; } // resourcesPath = AniResourceUtilities::getInstance().getFullPathForDownloadedFile(resourcesPath); //TODO moze to gdzies wyrzucic jednak albo chociaz dac parameter czy to robic? if(className == "PlainNode"){ newObject = AniPlainNode::create(); } else if(className == "ColourLayer"){ auto colour = AniJSONParseUtils::getColor3B(nodeData["colour"]); cocos2d::Color4B col = cocos2d::Color4B(colour,255); newObject = AniPlainNode::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 = AniPlainLabel::create(text, fontPath, fontSize); } else if(className == "ChangingSprite") { std::string filePath1 = resourcesPath + nodeData["imagePath1"].GetString(); std::string filePath2 = resourcesPath + nodeData["imagePath2"].GetString(); newObject = AniChangingSprite::createWithSpritePaths(filePath1, filePath2); } else if(className == "ContainerSprite") { std::string filePath = resourcesPath + nodeData["imagePath"].GetString(); newObject = AniContainerSprite::createWithAniContainerSpritePath(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 = AniPlainSprite::createWithSpritePath(filePath); } else if(className == "SimpleButton"){ newObject = AniSimpleButton::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() ? AniLevelView::create(path) : AniLevelView::create(path, levelName); } else if (className == "LevelPickerView"){ auto levelImagePaths = AniJSONParseUtils::parseStringArray(nodeData["levelImagePaths"]); auto levelNames = nodeData.HasMember("levelNames") ? AniJSONParseUtils::parseStringArray(nodeData["levelNames"]) : std::vector(); for(auto& path : levelImagePaths){ path = resourcesPath + path; } newObject = AniLevelPickerView::create(levelImagePaths, levelNames); } else if(className == "TwoStateButton"){ std::string inactiveImagePath = resourcesPath + nodeData["inactiveImagePath"].GetString(); std::string activeImagePath = resourcesPath + nodeData["activeImagePath"].GetString(); newObject = AniTwoStateButton::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" ? AniProgressSliderNodeOrientationVertical : AniProgressSliderNodeOrientationHorizontal; newObject = AniProgressSliderNode::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 = AniPlainSprite::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 = AniPlainSprite::createWithSpritePath(filePath); } return newObject; } }; void AniLayoutParser::loadLayoutFromJSONFile(const std::string& jsonFilePath, AniLayoutViewInterface* scene) { std::string jsonString = AniJSONParseUtils::loadJSONFromFile(jsonFilePath); return this->loadLayoutFromJSONString(jsonString, scene); } void AniLayoutParser::loadLayoutFromDownloadedJSONFile(const std::string& jsonFilePath, AniLayoutViewInterface* scene) { // std::string downloadedFilePath = AniResourceUtilities::getInstance().getFullPathForDownloadedFile(jsonFilePath, true); std::string jsonString = cocos2d::FileUtils::getInstance()->getStringFromFile(jsonFilePath); //this->loadJSONFromFile(downloadedFilePath + jsonFilePath); return this->loadLayoutFromJSONString(jsonString, scene); } void AniLayoutParser::loadLayoutFromJSONString(const std::string& jsonString, AniLayoutViewInterface* 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(); scene->setupBackgroundMusic(bgMusicFilePath); // AniSoundsRepo::preloadSoundEffect(bgMusicFilePath); AniSoundsRepo::preloadMusic(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("mapPath")){ auto mapPath = layerJSON["mapPath"].GetString(); const auto& mapObjectsJSON = layerJSON["customObjects"]; // std::vector mapObjects; // for(const auto& mapObjectJSON : mapObjectsJSON) // { // MapObject* mapObject = new MapObject(); // TODO memory leaks; add delete? use profiler? // mapObject->parseProperties(mapObjectJSON, ); // mapObjects.push_back(mapObject); // } std::string fullMapPath = mapPath; newLayer = AniMapLayer::create(resFolder, fullMapPath, &mapObjectsJSON); // delete mapProperties; // for(MapObject* object : mapObjects){ // delete object; // } } else { 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 = AniResourceUtilities::getInstance().getFullPathForDownloadedFile(resFolder); altResFolder = AniResourceUtilities::getInstance().getFullPathForDownloadedFile(altResFolder); rapidjson::Value layerJSONCopy(layerJSON, this->_parsedDocument->GetAllocator()); this->parseChildObjects(scene, layerJSONCopy, newLayer, objectLayoutsFolder, resFolder, altResFolder, true); } this->_parsedDocument = NULL; } void AniLayoutParser::parseChildObjects(AniLayoutViewInterface* 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* AniLayoutParser::parseObject(AniLayoutViewInterface* 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 = AniResourceUtilities::getInstance().getFullPathForDownloadedFile(objectDataPath, true); std::string jsonString = AniJSONParseUtils::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); } } } AniLayoutObjectMapper mapper; std::string type = "PlainSprite"; auto typeValue = this->getValueForKey("type", nodeData); if(typeValue != NULL){ type = typeValue->GetString(); } auto newObjectAsAniLayoutObject = mapper.createObjectFromClassName(type, nodeData, resFolder, altResFolder); if(newObjectAsAniLayoutObject == NULL){ return NULL; } newObjectAsAniLayoutObject->loadPropertiesFromJSON(nodeData, scene, resFolder, altResFolder); newObjectAsAniLayoutObject->prepareSize(nodeData, newObjectWidth, newObjectHeight); newObject = dynamic_cast(newObjectAsAniLayoutObject); if(newObjectAsAniLayoutObject->isWidget()){ newObjectAsAniLayoutObject->setOnTouchBeganCallback(CC_CALLBACK_2(AniLayoutViewInterface::touchHandlerForWidget, scene)); newObjectAsAniLayoutObject->setOnTouchEndedCallback(CC_CALLBACK_2(AniLayoutViewInterface::touchHandlerForWidget, scene)); newObjectAsAniLayoutObject->setOnTouchCancelledCallback(CC_CALLBACK_2(AniLayoutViewInterface::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/cocos2d::Director::getInstance()->getContentScaleFactor(); float additionalPaddingW = isTopLayer /*&& !ignoreScaling */? AniScalingUtils::getInstance().getScaledScreenSurplusWidth()*scaleToDesignSize / 2 : 0; float additionalPaddingH = isTopLayer /*&& !ignoreScaling */? AniScalingUtils::getInstance().getScaledScreenSurplusHeight()*scaleToDesignSize / 2 : 0; // float scaleToDesignSize = AniScalingUtils::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 AniSimpleButton OBJECTS, FIX if(AniScalingUtils::isSmallDevice()){ if((AniScalingUtils::isElementTooSmallForSmallDevice(newObjectWidth) && (type == "ValueStorage" || type == "SimpleButton")) || (AniJSONParseUtils::hasMemberBool(nodeData, "scaleUpForSmallDevices") && nodeData["scaleUpForSmallDevices"].GetBool() == true)){ objectScale *= AniScalingUtils::getScaleForSmallDevice(); } } // if(AniScalingUtils::isSmallDevice() && (type == "AniValueStorage" || type == "AniSimpleButton")){//TODO ugly, should probably be somewhere in the AniSimpleButton class or somewhere // objectScale = objectScale * AniScalingUtils::getScaleForSmallDevice(); // } // if(AniScalingUtils::isSmallDevice() && AniScalingUtils::isElementTooSmallForSmallDevice(newObject->getBoundingBox().size.width)){// && (type == "AniValueStorage" || type == "AniSimpleButton")){//TODO ugly, should probably be somewhere in the AniSimpleButton class or somewhere // objectScale = objectScale * AniScalingUtils::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 = AniStringUtils::splitString(xPlacementMode, ' '); float padding = 0; float width = atof(tokens[1].c_str())*AniScalingUtils::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 = AniStringUtils::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 = AniStringUtils::splitString(yPlacementMode, ' '); float padding = 0; float height = atof(tokens[1].c_str())*AniScalingUtils::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 = AniStringUtils::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); newObjectAsAniLayoutObject->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 = AniScalingUtils::imageAspectFillGetScale(cocos2d::Size(newObjectWidth, newObjectHeight), cocos2d::Size(parentNodeWidth, parentNodeHeight)); // newObject->setScale(stretchScale); if(AniJSONParseUtils::hasMemberFloat(nodeData, "thresholdAspectRatio")){ auto threshold = nodeData["thresholdAspectRatio"].GetFloat(); if(AniScalingUtils::getDeviceAspectRatio() > threshold){ auto adjustedParentWidth = parentNodeHeight*threshold; stretchScale = AniScalingUtils::imageAspectFillGetScale(cocos2d::Size(newObjectWidth, newObjectHeight), cocos2d::Size(adjustedParentWidth, parentNodeHeight)); } } newObject->setContentSize(cocos2d::Size(newObjectWidth*stretchScale, newObjectHeight*stretchScale)); } else if(aspectFit == stretchMode->GetString()){ stretchScale = AniScalingUtils::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* AniLayoutParser::getValueForKey(std::string key, const rapidjson::Value& mainNodeData) { if(mainNodeData.HasMember(key.c_str())){ return &mainNodeData[key.c_str()]; } return NULL; }