add animation buttons
This commit is contained in:
1 parent
112684b40a
commit
528e7ae5a6
5 files changed
+537
-40
No files matched your search
+326
-4
@@ -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<bool>();
|
||||
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::string>();
|
||||
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<float>();
|
||||
s.toY = step["to"][1].get<float>();
|
||||
}
|
||||
if (step.contains("duration")) {
|
||||
s.durationMs = step["duration"].get<float>() * 1000.0f;
|
||||
}
|
||||
if (step.contains("easing") && step["easing"].is_string()) {
|
||||
s.easing = step["easing"].get<std::string>();
|
||||
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<UiNode> findNodeByName(const std::shared_ptr<UiNode>& 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<std::pair<std::shared_ptr<UiNode>, size_t>> animationsToRemove;
|
||||
std::vector<std::function<void()>> 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<void(const std::shared_ptr<UiNode>&)> traverse = [&](const std::shared_ptr<UiNode>& 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<void()> cb) {
|
||||
animCallbacks[{nodeName, animName}] = std::move(cb);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
Reference in new issue
Block a user