#pragma once #include "render/Renderer.h" #include "render/TextureManager.h" #include "Environment.h" #include "external/nlohmann/json.hpp" #include #include #include #include #include #include #include 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 texNormal; std::shared_ptr texHover; std::shared_ptr texPressed; ButtonState state = ButtonState::Normal; VertexRenderStruct mesh; 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; }; struct UiSlider { std::string name; UiRect rect; std::shared_ptr texTrack; std::shared_ptr texKnob; VertexRenderStruct trackMesh; VertexRenderStruct knobMesh; float value = 0.0f; bool vertical = true; std::function 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 texBase; // текстура основания std::shared_ptr 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> children; std::shared_ptr button; 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 { 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 findButton(const std::string& name); bool setButtonCallback(const std::string& name, std::function 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 findSlider(const std::string& name); bool setSliderCallback(const std::string& name, std::function 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 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 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::vector> joysticks; std::map, std::vector> nodeActiveAnims; std::map, std::function> animCallbacks; // key: (nodeName, animName) std::shared_ptr pressedButton; std::shared_ptr pressedSlider; std::shared_ptr pressedJoystick; struct MenuState { std::shared_ptr root; std::vector> buttons; std::vector> sliders; std::shared_ptr pressedButton; std::shared_ptr pressedSlider; std::string path; std::map, std::function> animCallbacks; }; std::vector menuStack; }; } // namespace ZL