add UiSlider and some fix

This commit is contained in:
Vlad 2025-12-23 13:37:54 +06:00
parent bee2fff538
commit d57112be35
5 changed files with 398 additions and 112 deletions

187
Game.cpp
View File

@ -167,31 +167,37 @@ namespace ZL
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) {
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.popMenu();
});
});
}
else {
std::cerr << "Failed to open settings menu" << std::endl;
}
});
});
uiManager.setSliderCallback("musicVolumeSlider", [this](const std::string& name, float value) {
std::cerr << "Music volume slider changed to: " << value << std::endl;
musicVolume = value;
Environment::shipVelocity = musicVolume * 20.0f;
});
cubemapTexture = std::make_shared<Texture>(
std::array<TextureDataStruct, 6>{
@ -237,62 +243,62 @@ namespace ZL
}
/* buttonTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/button.png", CONST_ZIP_FILE));
/* buttonTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/button.png", CONST_ZIP_FILE));
button.data.PositionData.push_back({ 100, 100, 0 });
button.data.PositionData.push_back({ 100, 150, 0 });
button.data.PositionData.push_back({ 300, 150, 0 });
button.data.PositionData.push_back({ 100, 100, 0 });
button.data.PositionData.push_back({ 300, 150, 0 });
button.data.PositionData.push_back({ 300, 100, 0 });
button.data.PositionData.push_back({ 100, 100, 0 });
button.data.PositionData.push_back({ 100, 150, 0 });
button.data.PositionData.push_back({ 300, 150, 0 });
button.data.PositionData.push_back({ 100, 100, 0 });
button.data.PositionData.push_back({ 300, 150, 0 });
button.data.PositionData.push_back({ 300, 100, 0 });
button.data.TexCoordData.push_back({ 0,0 });
button.data.TexCoordData.push_back({ 0,1 });
button.data.TexCoordData.push_back({ 1,1 });
button.data.TexCoordData.push_back({ 0,0 });
button.data.TexCoordData.push_back({ 1,1 });
button.data.TexCoordData.push_back({ 1,0 });
button.data.TexCoordData.push_back({ 0,0 });
button.data.TexCoordData.push_back({ 0,1 });
button.data.TexCoordData.push_back({ 1,1 });
button.data.TexCoordData.push_back({ 0,0 });
button.data.TexCoordData.push_back({ 1,1 });
button.data.TexCoordData.push_back({ 1,0 });
button.RefreshVBO();*/
button.RefreshVBO();*/
/*
musicVolumeBarTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/musicVolumeBarTexture.png", CONST_ZIP_FILE));
musicVolumeBarTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/musicVolumeBarTexture.png", CONST_ZIP_FILE));
musicVolumeBar.data.PositionData.push_back({ 1190, 100, 0 });
musicVolumeBar.data.PositionData.push_back({ 1190, 600, 0 });
musicVolumeBar.data.PositionData.push_back({ 1200, 600, 0 });
musicVolumeBar.data.PositionData.push_back({ 1190, 100, 0 });
musicVolumeBar.data.PositionData.push_back({ 1200, 600, 0 });
musicVolumeBar.data.PositionData.push_back({ 1200, 100, 0 });
musicVolumeBar.data.PositionData.push_back({ 1190, 100, 0 });
musicVolumeBar.data.PositionData.push_back({ 1190, 600, 0 });
musicVolumeBar.data.PositionData.push_back({ 1200, 600, 0 });
musicVolumeBar.data.PositionData.push_back({ 1190, 100, 0 });
musicVolumeBar.data.PositionData.push_back({ 1200, 600, 0 });
musicVolumeBar.data.PositionData.push_back({ 1200, 100, 0 });
musicVolumeBar.data.TexCoordData.push_back({ 0,0 });
musicVolumeBar.data.TexCoordData.push_back({ 0,1 });
musicVolumeBar.data.TexCoordData.push_back({ 1,1 });
musicVolumeBar.data.TexCoordData.push_back({ 0,0 });
musicVolumeBar.data.TexCoordData.push_back({ 1,1 });
musicVolumeBar.data.TexCoordData.push_back({ 1,0 });
musicVolumeBar.data.TexCoordData.push_back({ 0,0 });
musicVolumeBar.data.TexCoordData.push_back({ 0,1 });
musicVolumeBar.data.TexCoordData.push_back({ 1,1 });
musicVolumeBar.data.TexCoordData.push_back({ 0,0 });
musicVolumeBar.data.TexCoordData.push_back({ 1,1 });
musicVolumeBar.data.TexCoordData.push_back({ 1,0 });
musicVolumeBar.RefreshVBO();
musicVolumeBar.RefreshVBO();
musicVolumeBarButtonTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/musicVolumeBarButton.png", CONST_ZIP_FILE));
musicVolumeBarButtonTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/musicVolumeBarButton.png", CONST_ZIP_FILE));
float musicVolumeBarButtonButtonCenterY = 350.0f;
float musicVolumeBarButtonButtonCenterY = 350.0f;
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX - musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY + musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.PositionData.push_back({ musicVolumeBarButtonButtonCenterX + musicVolumeBarButtonButtonRadius, musicVolumeBarButtonButtonCenterY - musicVolumeBarButtonButtonRadius, 0 });
musicVolumeBarButton.data.TexCoordData.push_back({ 0,0 });
musicVolumeBarButton.data.TexCoordData.push_back({ 0,1 });
musicVolumeBarButton.data.TexCoordData.push_back({ 1,1 });
musicVolumeBarButton.data.TexCoordData.push_back({ 0,0 });
musicVolumeBarButton.data.TexCoordData.push_back({ 1,1 });
musicVolumeBarButton.data.TexCoordData.push_back({ 1,0 });
musicVolumeBarButton.data.TexCoordData.push_back({ 0,0 });
musicVolumeBarButton.data.TexCoordData.push_back({ 0,1 });
musicVolumeBarButton.data.TexCoordData.push_back({ 1,1 });
musicVolumeBarButton.data.TexCoordData.push_back({ 0,0 });
musicVolumeBarButton.data.TexCoordData.push_back({ 1,1 });
musicVolumeBarButton.data.TexCoordData.push_back({ 1,0 });
musicVolumeBarButton.RefreshVBO();
musicVolumeBarButton.RefreshVBO();*/
renderer.InitOpenGL();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@ -412,7 +418,7 @@ namespace ZL
renderer.shaderManager.PopShader();
CheckGlError();
}
void Game::UpdateVolumeKnob() {
/*void Game::UpdateVolumeKnob() {
float musicVolumeBarButtonButtonCenterY = volumeBarMinY + musicVolume * (volumeBarMaxY - volumeBarMinY);
auto& pos = musicVolumeBarButton.data.PositionData;
@ -427,7 +433,6 @@ namespace ZL
musicVolumeBarButton.RefreshVBO();
}
void Game::UpdateVolumeFromMouse(int mouseX, int mouseY) {
int uiX = mouseX;
@ -443,7 +448,8 @@ namespace ZL
if (t > 1.0f) t = 1.0f;
musicVolume = t;
UpdateVolumeKnob();
}
}*/
void Game::drawUI()
{
static const std::string defaultShaderName = "default";
@ -452,7 +458,6 @@ namespace ZL
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
glClear(GL_DEPTH_BUFFER_BIT);
renderer.shaderManager.PushShader(defaultShaderName);
@ -460,22 +465,24 @@ namespace ZL
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1);
renderer.PushMatrix();
//renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1);
//renderer.PushMatrix();
renderer.LoadIdentity();
//renderer.LoadIdentity();
//glBindTexture(GL_TEXTURE_2D, buttonTexture->getTexID());
//renderer.DrawVertexRenderStruct(button);
glBindTexture(GL_TEXTURE_2D, musicVolumeBarTexture->getTexID());
renderer.DrawVertexRenderStruct(musicVolumeBar);
glBindTexture(GL_TEXTURE_2D, musicVolumeBarButtonTexture->getTexID());
renderer.DrawVertexRenderStruct(musicVolumeBarButton);
//glBindTexture(GL_TEXTURE_2D, musicVolumeBarTexture->getTexID());
//renderer.DrawVertexRenderStruct(musicVolumeBar);
//glBindTexture(GL_TEXTURE_2D, musicVolumeBarButtonTexture->getTexID());
//renderer.DrawVertexRenderStruct(musicVolumeBarButton);
//renderer.PopMatrix();
//renderer.PopProjectionMatrix();
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
uiManager.draw(renderer);
@ -602,36 +609,39 @@ namespace ZL
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
Environment::exitGameLoop = true;
}
else if (event.type == SDL_MOUSEBUTTONDOWN) {
// 1. Îáðàáîòêà íàæàòèÿ êíîïêè ìûøè
int mx = event.button.x;
int my = event.button.y;
std::cout << mx << " " << my << '\n';
int uiX = mx;
int uiY = Environment::height - my;
uiManager.onMouseMove(uiX, uiY);
uiManager.onMouseDown(uiX, uiY);
if (uiX >= volumeBarMinX - 40 && uiX <= volumeBarMaxX + 40 &&
uiY >= volumeBarMinY - 40 && uiY <= volumeBarMaxY + 40) {
isDraggingVolume = true;
UpdateVolumeFromMouse(mx, my);
}
else {
Environment::tapDownHold = true;
// Êîîðäèíàòû íà÷àëüíîãî íàæàòèÿ
Environment::tapDownStartPos.v[0] = event.button.x;
Environment::tapDownStartPos.v[1] = event.button.y;
// Íà÷àëüíàÿ ïîçèöèÿ òàêæå ñòàíîâèòñÿ òåêóùåé
Environment::tapDownCurrentPos.v[0] = event.button.x;
Environment::tapDownCurrentPos.v[1] = event.button.y;
bool uiHandled = false;
for (const auto& button : uiManager.findButton("") ? std::vector<std::shared_ptr<UiButton>>{} : std::vector<std::shared_ptr<UiButton>>{}) {
(void)button;
}
auto pressedSlider = [&]() -> std::shared_ptr<UiSlider> {
for (const auto& slider : uiManager.findSlider("") ? std::vector<std::shared_ptr<UiSlider>>{} : std::vector<std::shared_ptr<UiSlider>>{}) {
(void)slider;
}
return nullptr;
}();
if (!uiManager.isUiInteraction()) {
Environment::tapDownHold = true;
// Êîîðäèíàòû íà÷àëüíîãî íàæàòèÿ
Environment::tapDownStartPos.v[0] = mx;
Environment::tapDownStartPos.v[1] = my;
// Íà÷àëüíàÿ ïîçèöèÿ òàêæå ñòàíîâèòñÿ òåêóùåé
Environment::tapDownCurrentPos.v[0] = mx;
Environment::tapDownCurrentPos.v[1] = my;
}
}
else if (event.type == SDL_MOUSEBUTTONUP) {
// 2. Îáðàáîòêà îòïóñêàíèÿ êíîïêè ìûøè
@ -642,27 +652,22 @@ namespace ZL
uiManager.onMouseUp(uiX, uiY);
isDraggingVolume = false;
Environment::tapDownHold = false;
if (!uiManager.isUiInteraction()) {
Environment::tapDownHold = false;
}
}
else if (event.type == SDL_MOUSEMOTION) {
// 3. Îáðàáîòêà ïåðåìåùåíèÿ ìûøè
int mx = event.motion.x;
int my = event.motion.y;
int uiX = mx;
int uiY = Environment::height - my;
uiManager.onMouseMove(uiX, uiY);
if (isDraggingVolume) {
// Äâèãàåì ìûøü ïî ñëàéäåðó — ìåíÿåì ãðîìêîñòü è ïîçèöèþ êðóæêà
UpdateVolumeFromMouse(mx, my);
}
if (Environment::tapDownHold) {
// Îáíîâëåíèå òåêóùåé ïîçèöèè, åñëè êíîïêà óäåðæèâàåòñÿ
Environment::tapDownCurrentPos.v[0] = event.motion.x;
Environment::tapDownCurrentPos.v[1] = event.motion.y;
if (Environment::tapDownHold && !uiManager.isUiInteraction()) {
Environment::tapDownCurrentPos.v[0] = mx;
Environment::tapDownCurrentPos.v[1] = my;
}
}
else if (event.type == SDL_MOUSEWHEEL) {

24
Game.h
View File

@ -50,26 +50,28 @@ namespace ZL {
std::vector<VertexRenderStruct> boxRenderArr;
std::shared_ptr<Texture> buttonTexture;
VertexRenderStruct button;
//std::shared_ptr<Texture> buttonTexture;
//VertexRenderStruct button;
std::shared_ptr<Texture> musicVolumeBarTexture;
VertexRenderStruct musicVolumeBar;
//std::shared_ptr<Texture> musicVolumeBarTexture;
//VertexRenderStruct musicVolumeBar;
std::shared_ptr<Texture> musicVolumeBarButtonTexture;
VertexRenderStruct musicVolumeBarButton;
//std::shared_ptr<Texture> musicVolumeBarButtonTexture;
//VertexRenderStruct musicVolumeBarButton;
bool isDraggingVolume = false;
//bool isDraggingVolume = false;
float musicVolume = 0.0f;
float volumeBarMinX = 1190.0f;
float volumeBarMaxX = 1200.0f;
float volumeBarMinY = 100.0f;
float volumeBarMaxY = 600.0f;
float musicVolumeBarButtonButtonCenterX = 1195.0f;
float musicVolumeBarButtonButtonRadius = 25.0f;
void UpdateVolumeFromMouse(int mouseX, int mouseY);
void UpdateVolumeKnob();
//float musicVolumeBarButtonButtonCenterX = 1195.0f;
//float musicVolumeBarButtonButtonRadius = 25.0f;
//void UpdateVolumeFromMouse(int mouseX, int mouseY);
//void UpdateVolumeKnob();
static const size_t CONST_TIMER_INTERVAL = 10;
static const size_t CONST_MAX_TIME_INTERVAL = 1000;

View File

@ -63,7 +63,94 @@ namespace ZL {
renderer.DisableVertexAttribArray(vTexCoordName);
}
// UiManager implementation
void UiSlider::buildTrackMesh() {
trackMesh.data.PositionData.clear();
trackMesh.data.TexCoordData.clear();
float x0 = rect.x;
float y0 = rect.y;
float x1 = rect.x + rect.w;
float y1 = rect.y + rect.h;
trackMesh.data.PositionData.push_back({ x0, y0, 0 });
trackMesh.data.TexCoordData.push_back({ 0, 0 });
trackMesh.data.PositionData.push_back({ x0, y1, 0 });
trackMesh.data.TexCoordData.push_back({ 0, 1 });
trackMesh.data.PositionData.push_back({ x1, y1, 0 });
trackMesh.data.TexCoordData.push_back({ 1, 1 });
trackMesh.data.PositionData.push_back({ x0, y0, 0 });
trackMesh.data.TexCoordData.push_back({ 0, 0 });
trackMesh.data.PositionData.push_back({ x1, y1, 0 });
trackMesh.data.TexCoordData.push_back({ 1, 1 });
trackMesh.data.PositionData.push_back({ x1, y0, 0 });
trackMesh.data.TexCoordData.push_back({ 1, 0 });
trackMesh.RefreshVBO();
}
void UiSlider::buildKnobMesh() {
knobMesh.data.PositionData.clear();
knobMesh.data.TexCoordData.clear();
float kw = vertical ? rect.w * 4.0f : rect.w * 0.5f;
float kh = vertical ? rect.w * 4.0f : rect.h * 0.5f;
float cx = rect.x + rect.w * 0.5f;
float cy = rect.y + (vertical ? (value * rect.h) : (rect.h * 0.5f));
float x0 = cx - kw * 0.5f;
float y0 = cy - kh * 0.5f;
float x1 = cx + kw * 0.5f;
float y1 = cy + kh * 0.5f;
knobMesh.data.PositionData.push_back({ x0, y0, 0 });
knobMesh.data.TexCoordData.push_back({ 0, 0 });
knobMesh.data.PositionData.push_back({ x0, y1, 0 });
knobMesh.data.TexCoordData.push_back({ 0, 1 });
knobMesh.data.PositionData.push_back({ x1, y1, 0 });
knobMesh.data.TexCoordData.push_back({ 1, 1 });
knobMesh.data.PositionData.push_back({ x0, y0, 0 });
knobMesh.data.TexCoordData.push_back({ 0, 0 });
knobMesh.data.PositionData.push_back({ x1, y1, 0 });
knobMesh.data.TexCoordData.push_back({ 1, 1 });
knobMesh.data.PositionData.push_back({ x1, y0, 0 });
knobMesh.data.TexCoordData.push_back({ 1, 0 });
knobMesh.RefreshVBO();
}
void UiSlider::draw(Renderer& renderer) const {
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
if (texTrack) {
glBindTexture(GL_TEXTURE_2D, texTrack->getTexID());
renderer.DrawVertexRenderStruct(trackMesh);
}
if (texKnob) {
glBindTexture(GL_TEXTURE_2D, texKnob->getTexID());
renderer.DrawVertexRenderStruct(knobMesh);
}
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
}
void UiManager::loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
std::ifstream in(path);
if (!in.is_open()) {
@ -88,11 +175,16 @@ namespace ZL {
root = parseNode(j["root"], renderer, zipFile);
layoutNode(root);
buttons.clear();
collectButtons(root);
sliders.clear();
collectButtonsAndSliders(root);
for (auto& b : buttons) {
b->buildMesh();
}
for (auto& s : sliders) {
s->buildTrackMesh();
s->buildKnobMesh();
}
}
std::shared_ptr<UiNode> UiManager::parseNode(const json& j, Renderer& renderer, const std::string& zipFile) {
@ -137,8 +229,42 @@ namespace ZL {
node->button = btn;
}
else if (node->type == "Slider") {
auto s = std::make_shared<UiSlider>();
s->name = node->name;
s->rect = node->rect;
if (!j.contains("textures") || !j["textures"].is_object()) {
std::cerr << "UiManager: Slider '" << s->name << "' missing textures" << std::endl;
throw std::runtime_error("UI slider textures missing");
}
auto t = j["textures"];
auto loadTex = [&](const std::string& key)->std::shared_ptr<Texture> {
if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>();
try {
auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str());
return std::make_shared<Texture>(data);
}
catch (const std::exception& e) {
std::cerr << "UiManager: failed load texture " << path << " : " << e.what() << std::endl;
throw std::runtime_error("UI texture load failed: " + path);
}
};
s->texTrack = loadTex("track");
s->texKnob = loadTex("knob");
if (j.contains("value")) s->value = j["value"].get<float>();
if (j.contains("orientation")) {
std::string orient = j["orientation"].get<std::string>();
std::transform(orient.begin(), orient.end(), orient.begin(), ::tolower);
s->vertical = (orient != "horizontal");
}
node->slider = s;
}
// parse children
if (j.contains("children") && j["children"].is_array()) {
for (const auto& ch : j["children"]) {
node->children.push_back(parseNode(ch, renderer, zipFile));
@ -181,11 +307,14 @@ namespace ZL {
}
}
void UiManager::collectButtons(const std::shared_ptr<UiNode>& node) {
void UiManager::collectButtonsAndSliders(const std::shared_ptr<UiNode>& node) {
if (node->button) {
buttons.push_back(node->button);
}
for (auto& c : node->children) collectButtons(c);
if (node->slider) {
sliders.push_back(node->slider);
}
for (auto& c : node->children) collectButtonsAndSliders(c);
}
bool UiManager::setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb) {
@ -198,11 +327,70 @@ namespace ZL {
return true;
}
bool UiManager::addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile,
const std::string& trackPath, const std::string& knobPath, float initialValue, bool vertical) {
auto s = std::make_shared<UiSlider>();
s->name = name;
s->rect = rect;
s->value = std::clamp(initialValue, 0.0f, 1.0f);
s->vertical = vertical;
try {
if (!trackPath.empty()) {
auto data = CreateTextureDataFromPng(trackPath.c_str(), zipFile.c_str());
s->texTrack = std::make_shared<Texture>(data);
}
if (!knobPath.empty()) {
auto data = CreateTextureDataFromPng(knobPath.c_str(), zipFile.c_str());
s->texKnob = std::make_shared<Texture>(data);
}
}
catch (const std::exception& e) {
std::cerr << "UiManager: addSlider failed to load textures: " << e.what() << std::endl;
return false;
}
s->buildTrackMesh();
s->buildKnobMesh();
sliders.push_back(s);
return true;
}
std::shared_ptr<UiSlider> UiManager::findSlider(const std::string& name) {
for (auto& s : sliders) if (s->name == name) return s;
return nullptr;
}
bool UiManager::setSliderCallback(const std::string& name, std::function<void(const std::string&, float)> cb) {
auto s = findSlider(name);
if (!s) {
std::cerr << "UiManager: setSliderCallback failed, slider not found: " << name << std::endl;
return false;
}
s->onValueChanged = std::move(cb);
return true;
}
bool UiManager::setSliderValue(const std::string& name, float value) {
auto s = findSlider(name);
if (!s) return false;
value = std::clamp(value, 0.0f, 1.0f);
if (fabs(s->value - value) < 1e-6f) return true;
s->value = value;
s->buildKnobMesh();
if (s->onValueChanged) s->onValueChanged(s->name, s->value);
return true;
}
bool UiManager::pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile) {
MenuState prev;
prev.root = root;
prev.buttons = buttons;
prev.sliders = sliders;
prev.pressedButton = pressedButton;
prev.pressedSlider = pressedSlider;
prev.path = "";
try {
@ -226,11 +414,16 @@ namespace ZL {
root = s.root;
buttons = s.buttons;
sliders = s.sliders;
pressedButton = s.pressedButton;
pressedSlider = s.pressedSlider;
for (auto& b : buttons) {
if (b) b->buildMesh();
}
for (auto& sl : sliders) {
if (sl) { sl->buildTrackMesh(); sl->buildKnobMesh(); }
}
return true;
}
@ -240,8 +433,6 @@ namespace ZL {
}
void UiManager::draw(Renderer& renderer) {
if (!root) return;
renderer.PushProjectionMatrix(Environment::width, Environment::height, -1, 1);
renderer.PushMatrix();
renderer.LoadIdentity();
@ -249,6 +440,9 @@ namespace ZL {
for (const auto& b : buttons) {
b->draw(renderer);
}
for (const auto& s : sliders) {
s->draw(renderer);
}
renderer.PopMatrix();
renderer.PopProjectionMatrix();
@ -263,6 +457,22 @@ namespace ZL {
if (b->state != ButtonState::Pressed) b->state = ButtonState::Normal;
}
}
if (pressedSlider) {
auto s = pressedSlider;
float t;
if (s->vertical) {
t = (y - s->rect.y) / s->rect.h;
}
else {
t = (x - s->rect.x) / s->rect.w;
}
if (t < 0.0f) t = 0.0f;
if (t > 1.0f) t = 1.0f;
s->value = t;
s->buildKnobMesh();
if (s->onValueChanged) s->onValueChanged(s->name, s->value);
}
}
void UiManager::onMouseDown(int x, int y) {
@ -271,8 +481,24 @@ namespace ZL {
b->state = ButtonState::Pressed;
pressedButton = b;
}
else {
// leave others
}
for (auto& s : sliders) {
if (s->rect.contains((float)x, (float)y)) {
pressedSlider = s;
float t;
if (s->vertical) {
t = (y - s->rect.y) / s->rect.h;
}
else {
t = (x - s->rect.x) / s->rect.w;
}
if (t < 0.0f) t = 0.0f;
if (t > 1.0f) t = 1.0f;
s->value = t;
s->buildKnobMesh();
if (s->onValueChanged) s->onValueChanged(s->name, s->value);
break;
}
}
}
@ -290,6 +516,10 @@ namespace ZL {
}
}
pressedButton.reset();
if (pressedSlider) {
pressedSlider.reset();
}
}
std::shared_ptr<UiButton> UiManager::findButton(const std::string& name) {

View File

@ -45,12 +45,32 @@ namespace ZL {
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 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;
};
@ -67,10 +87,21 @@ namespace ZL {
void onMouseDown(int x, int y);
void onMouseUp(int x, int y);
bool isUiInteraction() const {
return pressedButton != nullptr || pressedSlider != nullptr;
}
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 pushMenuFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
bool popMenu();
void clearMenuStack();
@ -78,17 +109,21 @@ namespace ZL {
private:
std::shared_ptr<UiNode> parseNode(const json& j, Renderer& renderer, const std::string& zipFile);
void layoutNode(const std::shared_ptr<UiNode>& node);
void collectButtons(const std::shared_ptr<UiNode>& node);
void collectButtonsAndSliders(const std::shared_ptr<UiNode>& node);
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;
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;
};

View File

@ -66,6 +66,20 @@
]
}
]
},
{
"type": "Slider",
"name": "musicVolumeSlider",
"x": 1140,
"y": 100,
"width": 10,
"height": 500,
"value": 0.5,
"orientation": "vertical",
"textures": {
"track": "./resources/musicVolumeBarTexture.png",
"knob": "./resources/musicVolumeBarButton.png"
}
}
]
}