space-game001/src/UiManager.h
Vladislav Khorev 1265d87bc5 Updated UI
2026-02-22 18:23:27 +03:00

260 lines
7.5 KiB
C++

#pragma once
#include "render/Renderer.h"
#include "render/TextureManager.h"
#include "render/TextRenderer.h"
#include "Environment.h"
#include "external/nlohmann/json.hpp"
#include <string>
#include <vector>
#include <memory>
#include <functional>
#include <map>
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 UiTextView {
std::string name;
UiRect rect;
std::string text = "";
std::string fontPath = "resources/fonts/DroidSans.ttf";
int fontSize = 32;
std::array<float, 4> color = { 1.f, 1.f, 1.f, 1.f }; // rgba
bool centered = true;
std::unique_ptr<TextRenderer> textRenderer;
void draw(Renderer& renderer) const {
if (textRenderer) {
textRenderer->drawText(text, rect.x + rect.w / 2, rect.y + rect.h / 2, 1.0f, centered, color);
}
}
};
struct UiTextField {
std::string name;
UiRect rect;
std::string text = "";
std::string placeholder = "";
std::string fontPath = "resources/fonts/DroidSans.ttf";
int fontSize = 32;
std::array<float, 4> color = { 1.f, 1.f, 1.f, 1.f };
std::array<float, 4> placeholderColor = { 0.5f, 0.5f, 0.5f, 1.f };
std::array<float, 4> backgroundColor = { 0.2f, 0.2f, 0.2f, 1.f };
std::array<float, 4> borderColor = { 0.5f, 0.5f, 0.5f, 1.f };
int maxLength = 256;
bool focused = false;
std::unique_ptr<TextRenderer> textRenderer;
std::function<void(const std::string&, const std::string&)> onTextChanged;
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::shared_ptr<UiTextView> textView;
std::shared_ptr<UiTextField> textField;
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;
};
std::shared_ptr<UiNode> parseNode(const json& j, Renderer& renderer, const std::string& zipFile);
std::shared_ptr<UiNode> loadUiFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
class UiManager {
public:
UiManager() = default;
void replaceRoot(std::shared_ptr<UiNode> newRoot);
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);
void onKeyPress(unsigned char key);
void onKeyBackspace();
bool isUiInteraction() const {
return pressedButton != nullptr || pressedSlider != nullptr || focusedTextField != 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)
std::shared_ptr<UiTextView> findTextView(const std::string& name);
bool setText(const std::string& name, const std::string& newText);
std::shared_ptr<UiTextField> findTextField(const std::string& name);
bool setTextFieldCallback(const std::string& name, std::function<void(const std::string&, const std::string&)> cb);
std::string getTextFieldValue(const std::string& name);
bool pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
bool pushMenuFromSavedRoot(std::shared_ptr<UiNode> newRoot);
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:
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<UiTextView>> textViews;
std::vector<std::shared_ptr<UiTextField>> textFields;
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<UiTextField> focusedTextField;
struct MenuState {
std::shared_ptr<UiNode> root;
std::vector<std::shared_ptr<UiButton>> buttons;
std::vector<std::shared_ptr<UiSlider>> sliders;
std::vector<std::shared_ptr<UiTextField>> textFields;
std::shared_ptr<UiButton> pressedButton;
std::shared_ptr<UiSlider> pressedSlider;
std::shared_ptr<UiTextField> focusedTextField;
std::string path;
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks;
};
std::vector<MenuState> menuStack;
};
} // namespace ZL