diff --git a/assets.zip b/assets.zip index b8c7ef4..1f44300 100644 Binary files a/assets.zip and b/assets.zip differ diff --git a/config/ui.json b/config/ui.json index c8753cf..974d34a 100644 --- a/config/ui.json +++ b/config/ui.json @@ -31,9 +31,25 @@ "y": 300, "width": 200, "height": 50, + "animations": { + "buttonsExit": { + "repeat": false, + "steps": [ + { + "type": "move", + "to": [ + -400, + 0 + ], + "duration": 1.0, + "easing": "easein" + } + ] + } + }, "textures": { "normal": "./resources/button.png", - "hover": "./resources/button.png", + "hover": "./resources/sand.png", "pressed": "./resources/button.png" } }, @@ -44,9 +60,29 @@ "y": 200, "width": 200, "height": 50, + "animations": { + "buttonsExit": { + "repeat": false, + "steps": [ + { + "type": "wait", + "duration": 0.5 + }, + { + "type": "move", + "to": [ + -400, + 0 + ], + "duration": 1.0, + "easing": "easein" + } + ] + } + }, "textures": { "normal": "./resources/sand.png", - "hover": "./resources/sand.png", + "hover": "./resources/button.png", "pressed": "./resources/sand.png" } }, @@ -57,9 +93,43 @@ "y": 100, "width": 200, "height": 50, + "animations": { + "buttonsExit": { + "repeat": false, + "steps": [ + { + "type": "wait", + "duration": 1.0 + }, + { + "type": "move", + "to": [ + -400, + 0 + ], + "duration": 1.0, + "easing": "easein" + } + ] + }, + "bgScroll": { + "repeat": true, + "steps": [ + { + "type": "move", + "to": [ + 1280, + 0 + ], + "duration": 5.0, + "easing": "linear" + } + ] + } + }, "textures": { "normal": "./resources/rock.png", - "hover": "./resources/rock.png", + "hover": "./resources/button.png", "pressed": "./resources/rock.png" } } diff --git a/src/Game.cpp b/src/Game.cpp index 703d148..939f907 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -18,7 +18,8 @@ namespace ZL const char* CONST_ZIP_FILE = "../assets.zip"; #endif - + static bool g_exitBgAnimating = false; + Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen) { @@ -42,7 +43,7 @@ namespace ZL const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE; const float MIN_COORD = -100.0f; const float MAX_COORD = 100.0f; - const int MAX_ATTEMPTS = 1000; + const int MAX_ATTEMPTS = 1000; std::vector boxCoordsArr; std::random_device rd; @@ -65,7 +66,7 @@ namespace ZL (float)distrib(gen) ); - accepted = true; + accepted = true; for (const auto& existingBox : boxCoordsArr) { @@ -76,7 +77,7 @@ namespace ZL if (distanceSquared < MIN_DISTANCE_SQUARED) { accepted = false; - break; + break; } } @@ -152,7 +153,7 @@ namespace ZL renderer.shaderManager.AddShaderFromFiles("default", "./shaders/default.vertex", "./shaders/default_desktop.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("env_sky", "./shaders/env_sky.vertex", "./shaders/env_sky_desktop.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "./shaders/defaultAtmosphere.vertex", "./shaders/defaultAtmosphere_desktop.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("planetBake", "./shaders/planet_bake.vertex", "./shaders/planet_bake_desktop.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetBake", "./shaders/planet_bake.vertex", "./shaders/planet_bake_desktop.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetStone", "./shaders/planet_stone.vertex", "./shaders/planet_stone_desktop.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetLand", "./shaders/planet_land.vertex", "./shaders/planet_land_desktop.fragment", CONST_ZIP_FILE); @@ -176,37 +177,73 @@ namespace ZL #endif #endif -//#ifndef SIMPLIFIED + //#ifndef SIMPLIFIED bool cfgLoaded = sparkEmitter.loadFromJsonFile("config/spark_config.json", renderer, CONST_ZIP_FILE); bool projCfgLoaded = projectileEmitter.loadFromJsonFile("config/spark_projectile_config.json", renderer, CONST_ZIP_FILE); projectileEmitter.setEmissionPoints(std::vector()); uiManager.loadFromFile("config/ui.json", renderer, CONST_ZIP_FILE); - uiManager.setButtonCallback("playButton", [this](const std::string& name) { - std::cerr << "Play button pressed: " << name << std::endl; - }); - - uiManager.setButtonCallback("exitButton", [](const std::string& name) { - Environment::exitGameLoop = true; - }); - - uiManager.setButtonCallback("settingsButton", [this](const std::string& name) { + uiManager.loadFromFile("config/ui.json", renderer, CONST_ZIP_FILE); + uiManager.startAnimationOnNode("backgroundNode", "bgScroll"); + static bool isExitButtonAnimating = false; + uiManager.setAnimationCallback("settingsButton", "buttonsExit", [this]() { + std::cerr << "Settings button animation finished -> переход в настройки" << std::endl; if (uiManager.pushMenuFromFile("config/settings.json", this->renderer, CONST_ZIP_FILE)) { - uiManager.setButtonCallback("Opt1", [this](const std::string& n) { std::cerr << "Opt1 pressed: " << n << std::endl; }); - uiManager.setButtonCallback("Opt2", [this](const std::string& n) { std::cerr << "Opt2 pressed: " << n << std::endl; }); - uiManager.setButtonCallback("backButton", [this](const std::string& n) { + uiManager.stopAllAnimations(); uiManager.popMenu(); }); } else { - std::cerr << "Failed to open settings menu" << std::endl; + std::cerr << "Failed to open settings menu after animations" << std::endl; + } + }); + + uiManager.setAnimationCallback("exitButton", "bgScroll", []() { + std::cerr << "Exit button bgScroll animation finished" << std::endl; + g_exitBgAnimating = false; + }); + + // Set UI button callbacks + uiManager.setButtonCallback("playButton", [this](const std::string& name) { + std::cerr << "Play button pressed: " << name << std::endl; + + }); + + uiManager.setButtonCallback("settingsButton", [this](const std::string& name) { + std::cerr << "Settings button pressed: " << name << std::endl; + uiManager.startAnimationOnNode("playButton", "buttonsExit"); + uiManager.startAnimationOnNode("settingsButton", "buttonsExit"); + uiManager.startAnimationOnNode("exitButton", "buttonsExit"); + }); + + uiManager.setButtonCallback("exitButton", [this](const std::string& name) { + std::cerr << "Exit button pressed: " << name << std::endl; + + if (!g_exitBgAnimating) { + std::cerr << "start repeat anim bgScroll on exitButton" << std::endl; + g_exitBgAnimating = true; + uiManager.startAnimationOnNode("exitButton", "bgScroll"); + } + else { + std::cerr << "stop repeat anim bgScroll on exitButton" << std::endl; + g_exitBgAnimating = false; + uiManager.stopAnimationOnNode("exitButton", "bgScroll"); + + auto exitButton = uiManager.findButton("exitButton"); + if (exitButton) { + exitButton->animOffsetX = 0.0f; + exitButton->animOffsetY = 0.0f; + exitButton->animScaleX = 1.0f; + exitButton->animScaleY = 1.0f; + exitButton->buildMesh(); + } } }); @@ -215,7 +252,7 @@ namespace ZL musicVolume = value; Environment::shipVelocity = musicVolume * 20.0f; }); -//#endif + //#endif cubemapTexture = std::make_shared( std::array{ @@ -234,8 +271,8 @@ namespace ZL //Load texture spaceshipTexture = std::make_unique(CreateTextureDataFromPng("./resources/DefaultMaterial_BaseColor_shine.png", CONST_ZIP_FILE)); spaceshipBase = LoadFromTextFile02("./resources/spaceship006.txt", CONST_ZIP_FILE); - spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix()); - + spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix(); + //spaceshipTexture = std::make_unique(CreateTextureDataFromPng("./resources/cap_D.png", CONST_ZIP_FILE)); //spaceshipBase = LoadFromTextFile02("./resources/spaceship006x.txt", CONST_ZIP_FILE); //spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix()); @@ -448,11 +485,11 @@ namespace ZL } } -//#ifndef SIMPLIFIED + //#ifndef SIMPLIFIED sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); -//#endif + //#endif renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vPositionName); @@ -615,10 +652,10 @@ namespace ZL drawShip(); drawBoxes(); -//#ifndef SIMPLIFIED + //#ifndef SIMPLIFIED drawUI(); -//#endif + //#endif CheckGlError(); } @@ -634,10 +671,10 @@ namespace ZL size_t delta = (newTickCount - lastTickCount > CONST_MAX_TIME_INTERVAL) ? CONST_MAX_TIME_INTERVAL : newTickCount - lastTickCount; -//#ifndef SIMPLIFIED - //gameObjects.updateScene(delta); + //#ifndef SIMPLIFIED + //gameObjects.updateScene(delta); sparkEmitter.update(static_cast(delta)); -//#endif + //#endif planetObject.update(static_cast(delta)); if (Environment::tapDownHold) { @@ -672,7 +709,7 @@ namespace ZL Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted; } -//#ifndef SIMPLIFIED + //#ifndef SIMPLIFIED for (auto& p : projectiles) { if (p && p->isActive()) { p->update(static_cast(delta), renderer); @@ -707,7 +744,9 @@ namespace ZL sparkEmitter.update(static_cast(delta)); projectileEmitter.update(static_cast(delta)); -//#endif + + uiManager.update(static_cast(delta)); + //#endif lastTickCount = newTickCount; } } @@ -765,7 +804,7 @@ namespace ZL uiManager.onMouseDown(uiX, uiY); bool uiHandled = false; -//#ifndef SIMPLIFIED + //#ifndef SIMPLIFIED if (event.button.button == SDL_BUTTON_LEFT && !uiManager.isUiInteraction()) { uint64_t now = SDL_GetTicks64(); if (now - lastProjectileFireTime >= static_cast(projectileCooldownMs)) { @@ -773,7 +812,7 @@ namespace ZL fireProjectiles(); } } -//#endif + //#endif for (const auto& button : uiManager.findButton("") ? std::vector>{} : std::vector>{}) { (void)button; } diff --git a/src/UiManager.cpp b/src/UiManager.cpp index 33d7a82..5187433 100644 --- a/src/UiManager.cpp +++ b/src/UiManager.cpp @@ -8,6 +8,17 @@ namespace ZL { using json = nlohmann::json; + static float applyEasing(const std::string& easing, float t) { + if (easing == "easein") { + return t * t; + } + else if (easing == "easeout") { + float inv = 1.0f - t; + return 1.0f - inv * inv; + } + return t; + } + void UiButton::buildMesh() { mesh.data.PositionData.clear(); mesh.data.TexCoordData.clear(); @@ -52,6 +63,10 @@ namespace ZL { static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; + renderer.PushMatrix(); + renderer.TranslateMatrix({ animOffsetX, animOffsetY, 0.0f }); + renderer.ScaleMatrix({ animScaleX, animScaleY, 1.0f }); + renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vTexCoordName); @@ -61,6 +76,7 @@ namespace ZL { renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vTexCoordName); + renderer.PopMatrix(); } void UiSlider::buildTrackMesh() { @@ -191,6 +207,8 @@ namespace ZL { sliders.clear(); collectButtonsAndSliders(root); + nodeActiveAnims.clear(); + for (auto& b : buttons) { b->buildMesh(); } @@ -278,6 +296,37 @@ namespace ZL { node->slider = s; } + if (j.contains("animations") && j["animations"].is_object()) { + for (auto it = j["animations"].begin(); it != j["animations"].end(); ++it) { + std::string animName = it.key(); + const auto& animDef = it.value(); + UiNode::AnimSequence seq; + if (animDef.contains("repeat") && animDef["repeat"].is_boolean()) seq.repeat = animDef["repeat"].get(); + if (animDef.contains("steps") && animDef["steps"].is_array()) { + for (const auto& step : animDef["steps"]) { + UiNode::AnimStep s; + if (step.contains("type") && step["type"].is_string()) { + s.type = step["type"].get(); + std::transform(s.type.begin(), s.type.end(), s.type.begin(), ::tolower); + } + if (step.contains("to") && step["to"].is_array() && step["to"].size() >= 2) { + s.toX = step["to"][0].get(); + s.toY = step["to"][1].get(); + } + if (step.contains("duration")) { + s.durationMs = step["duration"].get() * 1000.0f; + } + if (step.contains("easing") && step["easing"].is_string()) { + s.easing = step["easing"].get(); + std::transform(s.easing.begin(), s.easing.end(), s.easing.begin(), ::tolower); + } + seq.steps.push_back(s); + } + } + node->animations[animName] = std::move(seq); + } + } + if (j.contains("children") && j["children"].is_array()) { for (const auto& ch : j["children"]) { node->children.push_back(parseNode(ch, renderer, zipFile)); @@ -406,13 +455,27 @@ namespace ZL { prev.pressedSlider = pressedSlider; prev.path = ""; + prev.animCallbacks = animCallbacks; + try { + nodeActiveAnims.clear(); + animCallbacks.clear(); + for (auto& b : buttons) { + if (b) { + b->animOffsetX = 0.0f; + b->animOffsetY = 0.0f; + b->animScaleX = 1.0f; + b->animScaleY = 1.0f; + } + } + loadFromFile(path, renderer, zipFile); menuStack.push_back(std::move(prev)); return true; } catch (const std::exception& e) { std::cerr << "UiManager: pushMenuFromFile failed to load " << path << " : " << e.what() << std::endl; + animCallbacks = prev.animCallbacks; return false; } } @@ -425,19 +488,32 @@ namespace ZL { auto s = menuStack.back(); menuStack.pop_back(); + nodeActiveAnims.clear(); + root = s.root; buttons = s.buttons; sliders = s.sliders; pressedButton = s.pressedButton; pressedSlider = s.pressedSlider; + animCallbacks = s.animCallbacks; + for (auto& b : buttons) { - if (b) b->buildMesh(); - } - for (auto& sl : sliders) { - if (sl) { sl->buildTrackMesh(); sl->buildKnobMesh(); } + if (b) { + b->animOffsetX = 0.0f; + b->animOffsetY = 0.0f; + b->animScaleX = 1.0f; + b->animScaleY = 1.0f; + b->buildMesh(); + } } + for (auto& sl : sliders) { + if (sl) { + sl->buildTrackMesh(); + sl->buildKnobMesh(); + } + } return true; } @@ -461,6 +537,168 @@ namespace ZL { renderer.PopProjectionMatrix(); } + static std::shared_ptr findNodeByName(const std::shared_ptr& node, const std::string& name) { + if (!node) return nullptr; + if (!name.empty() && node->name == name) return node; + for (auto& c : node->children) { + auto r = findNodeByName(c, name); + if (r) return r; + } + return nullptr; + } + + void UiManager::update(float deltaMs) { + if (!root) return; + + std::vector, size_t>> animationsToRemove; + std::vector> pendingCallbacks; + + for (auto& kv : nodeActiveAnims) { + auto node = kv.first; + auto& activeList = kv.second; + + for (size_t i = 0; i < activeList.size(); ++i) { + auto& act = activeList[i]; + if (!act.seq) { + animationsToRemove.push_back({ node, i }); + continue; + } + + const auto& steps = act.seq->steps; + + if (act.stepIndex >= steps.size()) { + if (act.repeat) { + if (node->button) { + node->button->animOffsetX = act.origOffsetX; + node->button->animOffsetY = act.origOffsetY; + node->button->animScaleX = act.origScaleX; + node->button->animScaleY = act.origScaleY; + } + act.stepIndex = 0; + act.elapsedMs = 0.0f; + act.stepStarted = false; + } + else { + if (act.onComplete) { + pendingCallbacks.push_back(act.onComplete); + } + animationsToRemove.push_back({ node, i }); + } + continue; + } + + const auto& step = steps[act.stepIndex]; + + if (step.durationMs <= 0.0f) { + if (step.type == "move") { + if (node->button) { + node->button->animOffsetX = step.toX; + node->button->animOffsetY = step.toY; + } + } + else if (step.type == "scale") { + if (node->button) { + node->button->animScaleX = step.toX; + node->button->animScaleY = step.toY; + } + } + act.stepIndex++; + act.elapsedMs = 0.0f; + act.stepStarted = false; + continue; + } + + if (!act.stepStarted && act.stepIndex == 0 && act.elapsedMs == 0.0f) { + if (node->button) { + act.origOffsetX = node->button->animOffsetX; + act.origOffsetY = node->button->animOffsetY; + act.origScaleX = node->button->animScaleX; + act.origScaleY = node->button->animScaleY; + } + else { + act.origOffsetX = act.origOffsetY = 0.0f; + act.origScaleX = act.origScaleY = 1.0f; + } + } + + float prevElapsed = act.elapsedMs; + act.elapsedMs += deltaMs; + + if (!act.stepStarted && prevElapsed == 0.0f) { + if (node->button) { + act.startOffsetX = node->button->animOffsetX; + act.startOffsetY = node->button->animOffsetY; + act.startScaleX = node->button->animScaleX; + act.startScaleY = node->button->animScaleY; + } + else { + act.startOffsetX = act.startOffsetY = 0.0f; + act.startScaleX = act.startScaleY = 1.0f; + } + if (step.type == "move") { + act.endOffsetX = step.toX; + act.endOffsetY = step.toY; + } + else if (step.type == "scale") { + act.endScaleX = step.toX; + act.endScaleY = step.toY; + } + act.stepStarted = true; + } + float t = (step.durationMs > 0.0f) ? (act.elapsedMs / step.durationMs) : 1.0f; + if (t > 1.0f) t = 1.0f; + float te = applyEasing(step.easing, t); + + if (step.type == "move") { + float nx = act.startOffsetX + (act.endOffsetX - act.startOffsetX) * te; + float ny = act.startOffsetY + (act.endOffsetY - act.startOffsetY) * te; + if (node->button) { + node->button->animOffsetX = nx; + node->button->animOffsetY = ny; + } + } + else if (step.type == "scale") { + float sx = act.startScaleX + (act.endScaleX - act.startScaleX) * te; + float sy = act.startScaleY + (act.endScaleY - act.startScaleY) * te; + if (node->button) { + node->button->animScaleX = sx; + node->button->animScaleY = sy; + } + } + else if (step.type == "wait") { + //wait + } + if (act.elapsedMs >= step.durationMs) { + act.stepIndex++; + act.elapsedMs = 0.0f; + act.stepStarted = false; + } + } + } + + for (auto it = animationsToRemove.rbegin(); it != animationsToRemove.rend(); ++it) { + auto& [node, index] = *it; + if (nodeActiveAnims.find(node) != nodeActiveAnims.end()) { + auto& animList = nodeActiveAnims[node]; + if (index < animList.size()) { + animList.erase(animList.begin() + index); + } + if (animList.empty()) { + nodeActiveAnims.erase(node); + } + } + } + + for (auto& cb : pendingCallbacks) { + try { + cb(); + } + catch (...) { + std::cerr << "UiManager: animation onComplete callback threw exception" << std::endl; + } + } + } + void UiManager::onMouseMove(int x, int y) { for (auto& b : buttons) { if (b->rect.contains((float)x, (float)y)) { @@ -540,4 +778,88 @@ namespace ZL { return nullptr; } + bool UiManager::startAnimationOnNode(const std::string& nodeName, const std::string& animName) { + if (!root) return false; + auto node = findNodeByName(root, nodeName); + if (!node) return false; + auto it = node->animations.find(animName); + if (it == node->animations.end()) return false; + + ActiveAnim aa; + aa.name = animName; + aa.seq = &it->second; + aa.stepIndex = 0; + aa.elapsedMs = 0.0f; + aa.repeat = it->second.repeat; + aa.stepStarted = false; + if (node->button) { + aa.origOffsetX = node->button->animOffsetX; + aa.origOffsetY = node->button->animOffsetY; + aa.origScaleX = node->button->animScaleX; + aa.origScaleY = node->button->animScaleY; + } + auto cbIt = animCallbacks.find({ nodeName, animName }); + if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second; + nodeActiveAnims[node].push_back(std::move(aa)); + return true; + } + + bool UiManager::stopAnimationOnNode(const std::string& nodeName, const std::string& animName) { + if (!root) return false; + auto node = findNodeByName(root, nodeName); + if (!node) return false; + auto it = nodeActiveAnims.find(node); + if (it != nodeActiveAnims.end()) { + auto& animList = it->second; + for (auto animIt = animList.begin(); animIt != animList.end(); ) { + if (animIt->name == animName) { + animIt = animList.erase(animIt); + } + else { + ++animIt; + } + } + if (animList.empty()) { + nodeActiveAnims.erase(it); + } + + return true; + } + + return false; + } + + void UiManager::startAnimation(const std::string& animName) { + if (!root) return; + std::function&)> traverse = [&](const std::shared_ptr& n) { + if (!n) return; + auto it = n->animations.find(animName); + if (it != n->animations.end()) { + ActiveAnim aa; + aa.name = animName; + aa.seq = &it->second; + aa.stepIndex = 0; + aa.elapsedMs = 0.0f; + aa.repeat = it->second.repeat; + aa.stepStarted = false; + if (n->button) { + aa.origOffsetX = n->button->animOffsetX; + aa.origOffsetY = n->button->animOffsetY; + aa.origScaleX = n->button->animScaleX; + aa.origScaleY = n->button->animScaleY; + } + auto cbIt = animCallbacks.find({ n->name, animName }); + if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second; + nodeActiveAnims[n].push_back(std::move(aa)); + } + for (auto& c : n->children) traverse(c); + }; + traverse(root); + } + + bool UiManager::setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function cb) { + animCallbacks[{nodeName, animName}] = std::move(cb); + return true; + } + } // namespace ZL \ No newline at end of file diff --git a/src/UiManager.h b/src/UiManager.h index dfee5aa..5891566 100644 --- a/src/UiManager.h +++ b/src/UiManager.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace ZL { @@ -41,6 +42,12 @@ namespace ZL { std::function onClick; + // animation runtime + float animOffsetX = 0.0f; + float animOffsetY = 0.0f; + float animScaleX = 1.0f; + float animScaleY = 1.0f; + void buildMesh(); void draw(Renderer& renderer) const; }; @@ -73,6 +80,19 @@ namespace ZL { std::shared_ptr slider; std::string orientation = "vertical"; float spacing = 0.0f; + + struct AnimStep { + std::string type; + float toX = 0.0f; + float toY = 0.0f; + float durationMs = 0.0f; + std::string easing = "linear"; + }; + struct AnimSequence { + std::vector steps; + bool repeat = false; + }; + std::map animations; }; class UiManager { @@ -91,6 +111,19 @@ namespace ZL { return pressedButton != nullptr || pressedSlider != nullptr; } + void stopAllAnimations() { + nodeActiveAnims.clear(); + + for (auto& b : buttons) { + if (b) { + b->animOffsetX = 0.0f; + b->animOffsetY = 0.0f; + b->animScaleX = 1.0f; + b->animScaleY = 1.0f; + } + } + } + std::shared_ptr findButton(const std::string& name); bool setButtonCallback(const std::string& name, std::function cb); @@ -106,15 +139,47 @@ namespace ZL { bool popMenu(); void clearMenuStack(); + void update(float deltaMs); + void startAnimation(const std::string& animName); + bool startAnimationOnNode(const std::string& nodeName, const std::string& animName); + bool stopAnimationOnNode(const std::string& nodeName, const std::string& animName); + bool setAnimationCallback(const std::string& nodeName, const std::string& animName, std::function cb); + private: std::shared_ptr parseNode(const json& j, Renderer& renderer, const std::string& zipFile); void layoutNode(const std::shared_ptr& node); void collectButtonsAndSliders(const std::shared_ptr& node); + struct ActiveAnim { + std::string name; + const UiNode::AnimSequence* seq = nullptr; + size_t stepIndex = 0; + float elapsedMs = 0.0f; + bool repeat = false; + float startOffsetX = 0.0f; + float startOffsetY = 0.0f; + float endOffsetX = 0.0f; + float endOffsetY = 0.0f; + float startScaleX = 1.0f; + float startScaleY = 1.0f; + float endScaleX = 1.0f; + float endScaleY = 1.0f; + std::function onComplete; + + float origOffsetX = 0.0f; + float origOffsetY = 0.0f; + float origScaleX = 1.0f; + float origScaleY = 1.0f; + bool stepStarted = false; + }; + std::shared_ptr root; std::vector> buttons; std::vector> sliders; + std::map, std::vector> nodeActiveAnims; + std::map, std::function> animCallbacks; // key: (nodeName, animName) + std::shared_ptr pressedButton; std::shared_ptr pressedSlider; @@ -125,6 +190,7 @@ namespace ZL { std::shared_ptr pressedButton; std::shared_ptr pressedSlider; std::string path; + std::map, std::function> animCallbacks; }; std::vector menuStack;