250 lines
7.2 KiB
C++
250 lines
7.2 KiB
C++
#pragma once
|
|
|
|
#include "render/Renderer.h"
|
|
#include "render/TextureManager.h"
|
|
#include "Environment.h"
|
|
#include "external/nlohmann/json.hpp"
|
|
#include <string>
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
|
|
namespace ZL {
|
|
|
|
using json = nlohmann::json;
|
|
|
|
struct UiRect {
|
|
float x = 0;
|
|
float y = 0;
|
|
float w = 0;
|
|
float h = 0;
|
|
bool contains(float px, float py) const {
|
|
return px >= x && px <= x + w && py >= y && py <= y + h;
|
|
}
|
|
};
|
|
|
|
enum class ButtonState {
|
|
Normal,
|
|
Hover,
|
|
Pressed
|
|
};
|
|
|
|
struct UiButton {
|
|
std::string name;
|
|
UiRect rect;
|
|
std::shared_ptr<Texture> texNormal;
|
|
std::shared_ptr<Texture> texHover;
|
|
std::shared_ptr<Texture> texPressed;
|
|
ButtonState state = ButtonState::Normal;
|
|
|
|
VertexRenderStruct mesh;
|
|
|
|
std::function<void(const std::string&)> 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;
|
|
};
|
|
|
|
struct UiSlider {
|
|
std::string name;
|
|
UiRect rect;
|
|
std::shared_ptr<Texture> texTrack;
|
|
std::shared_ptr<Texture> texKnob;
|
|
|
|
VertexRenderStruct trackMesh;
|
|
VertexRenderStruct knobMesh;
|
|
|
|
float value = 0.0f;
|
|
bool vertical = true;
|
|
|
|
std::function<void(const std::string&, float)> onValueChanged;
|
|
|
|
void buildTrackMesh();
|
|
void buildKnobMesh();
|
|
void draw(Renderer& renderer) const;
|
|
};
|
|
|
|
struct UiJoystick {
|
|
std::string name;
|
|
float centerX = 0; // центр джойстика (UI координаты)
|
|
float centerY = 0;
|
|
float baseRadius = 100; // радиус основания
|
|
float knobRadius = 40; // радиус ручки
|
|
|
|
float knobOffsetX = 0; // смещение ручки от центра
|
|
float knobOffsetY = 0;
|
|
|
|
bool isActive = false; // нажат ли джойстик
|
|
|
|
std::shared_ptr<Texture> texBase; // текстура основания
|
|
std::shared_ptr<Texture> texKnob; // текстура ручки
|
|
|
|
VertexRenderStruct baseMesh;
|
|
VertexRenderStruct knobMesh;
|
|
|
|
// Возвращает направление -1..1 по каждой оси
|
|
float getDirectionX() const { return baseRadius > 0 ? knobOffsetX / baseRadius : 0; }
|
|
float getDirectionY() const { return baseRadius > 0 ? -knobOffsetY / baseRadius : 0; }
|
|
|
|
// Возвращает силу 0..1
|
|
float getMagnitude() const {
|
|
if (baseRadius <= 0) return 0;
|
|
float dist = std::sqrt(knobOffsetX * knobOffsetX + knobOffsetY * knobOffsetY);
|
|
float ratio = dist / baseRadius;
|
|
return ratio < 1.0f ? ratio : 1.0f;
|
|
}
|
|
|
|
bool contains(float px, float py) const {
|
|
float dx = px - centerX;
|
|
float dy = py - centerY;
|
|
return (dx * dx + dy * dy) <= (baseRadius * baseRadius);
|
|
}
|
|
|
|
void buildBaseMesh();
|
|
void buildKnobMesh();
|
|
void draw(Renderer& renderer) const;
|
|
};
|
|
|
|
struct UiNode {
|
|
std::string type;
|
|
UiRect rect;
|
|
std::string name;
|
|
std::vector<std::shared_ptr<UiNode>> children;
|
|
std::shared_ptr<UiButton> button;
|
|
std::shared_ptr<UiSlider> 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<AnimStep> steps;
|
|
bool repeat = false;
|
|
};
|
|
std::map<std::string, AnimSequence> animations;
|
|
};
|
|
|
|
class UiManager {
|
|
public:
|
|
UiManager() = default;
|
|
|
|
void loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
|
|
|
|
void draw(Renderer& renderer);
|
|
|
|
void onMouseMove(int x, int y);
|
|
void onMouseDown(int x, int y);
|
|
void onMouseUp(int x, int y);
|
|
|
|
bool isUiInteraction() const {
|
|
return pressedButton != nullptr || pressedSlider != nullptr || pressedJoystick != 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<UiButton> findButton(const std::string& name);
|
|
|
|
bool setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb);
|
|
|
|
bool addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile,
|
|
const std::string& trackPath, const std::string& knobPath, float initialValue = 0.0f, bool vertical = true);
|
|
|
|
std::shared_ptr<UiSlider> findSlider(const std::string& name);
|
|
bool setSliderCallback(const std::string& name, std::function<void(const std::string&, float)> cb);
|
|
bool setSliderValue(const std::string& name, float value); // programmatic set (clamped 0..1)
|
|
|
|
bool addJoystick(const std::string& name, float centerX, float centerY, float baseRadius, float knobRadius,
|
|
Renderer& renderer, const std::string& zipFile,
|
|
const std::string& basePath, const std::string& knobPath);
|
|
std::shared_ptr<UiJoystick> findJoystick(const std::string& name);
|
|
void resetJoystick(const std::string& name); // сбросить ручку в центр
|
|
|
|
bool pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
|
|
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<void()> cb);
|
|
|
|
private:
|
|
std::shared_ptr<UiNode> parseNode(const json& j, Renderer& renderer, const std::string& zipFile);
|
|
void layoutNode(const std::shared_ptr<UiNode>& node);
|
|
void collectButtonsAndSliders(const std::shared_ptr<UiNode>& 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<void()> onComplete;
|
|
|
|
float origOffsetX = 0.0f;
|
|
float origOffsetY = 0.0f;
|
|
float origScaleX = 1.0f;
|
|
float origScaleY = 1.0f;
|
|
bool stepStarted = false;
|
|
};
|
|
|
|
std::shared_ptr<UiNode> root;
|
|
std::vector<std::shared_ptr<UiButton>> buttons;
|
|
std::vector<std::shared_ptr<UiSlider>> sliders;
|
|
std::vector<std::shared_ptr<UiJoystick>> joysticks;
|
|
|
|
std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims;
|
|
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName)
|
|
|
|
std::shared_ptr<UiButton> pressedButton;
|
|
std::shared_ptr<UiSlider> pressedSlider;
|
|
std::shared_ptr<UiJoystick> pressedJoystick;
|
|
|
|
struct MenuState {
|
|
std::shared_ptr<UiNode> root;
|
|
std::vector<std::shared_ptr<UiButton>> buttons;
|
|
std::vector<std::shared_ptr<UiSlider>> sliders;
|
|
std::shared_ptr<UiButton> pressedButton;
|
|
std::shared_ptr<UiSlider> pressedSlider;
|
|
std::string path;
|
|
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks;
|
|
};
|
|
|
|
std::vector<MenuState> menuStack;
|
|
};
|
|
|
|
} // namespace ZL
|