diff --git a/Game.cpp b/Game.cpp index aaf08a7..616bdc5 100755 --- a/Game.cpp +++ b/Game.cpp @@ -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( std::array{ @@ -237,62 +243,62 @@ namespace ZL } - /* buttonTexture = std::make_unique(CreateTextureDataFromPng("./resources/button.png", CONST_ZIP_FILE)); + /* buttonTexture = std::make_unique(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(CreateTextureDataFromPng("./resources/musicVolumeBarTexture.png", CONST_ZIP_FILE)); - musicVolumeBarTexture = std::make_unique(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(CreateTextureDataFromPng("./resources/musicVolumeBarButton.png", CONST_ZIP_FILE)); + musicVolumeBarButtonTexture = std::make_unique(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::vector>{}) { + (void)button; } + auto pressedSlider = [&]() -> std::shared_ptr { + for (const auto& slider : uiManager.findSlider("") ? std::vector>{} : std::vector>{}) { + (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) { diff --git a/Game.h b/Game.h index 5308799..80d938d 100755 --- a/Game.h +++ b/Game.h @@ -50,26 +50,28 @@ namespace ZL { std::vector boxRenderArr; - std::shared_ptr buttonTexture; - VertexRenderStruct button; + //std::shared_ptr buttonTexture; + //VertexRenderStruct button; - std::shared_ptr musicVolumeBarTexture; - VertexRenderStruct musicVolumeBar; + //std::shared_ptr musicVolumeBarTexture; + //VertexRenderStruct musicVolumeBar; - std::shared_ptr musicVolumeBarButtonTexture; - VertexRenderStruct musicVolumeBarButton; + //std::shared_ptr 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; diff --git a/UiManager.cpp b/UiManager.cpp index 670429c..27fbb25 100644 --- a/UiManager.cpp +++ b/UiManager.cpp @@ -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 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(); + 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 { + if (!t.contains(key) || !t[key].is_string()) return nullptr; + std::string path = t[key].get(); + try { + auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str()); + return std::make_shared(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(); + if (j.contains("orientation")) { + std::string orient = j["orientation"].get(); + 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& node) { + void UiManager::collectButtonsAndSliders(const std::shared_ptr& 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 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(); + 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(data); + } + if (!knobPath.empty()) { + auto data = CreateTextureDataFromPng(knobPath.c_str(), zipFile.c_str()); + s->texKnob = std::make_shared(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 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 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 UiManager::findButton(const std::string& name) { diff --git a/UiManager.h b/UiManager.h index 2ea6723..cee34af 100644 --- a/UiManager.h +++ b/UiManager.h @@ -45,12 +45,32 @@ namespace ZL { 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 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; }; @@ -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 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 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 parseNode(const json& j, Renderer& renderer, const std::string& zipFile); void layoutNode(const std::shared_ptr& node); - void collectButtons(const std::shared_ptr& node); + void collectButtonsAndSliders(const std::shared_ptr& node); std::shared_ptr root; std::vector> buttons; + std::vector> sliders; std::shared_ptr pressedButton; + std::shared_ptr pressedSlider; struct MenuState { std::shared_ptr root; std::vector> buttons; + std::vector> sliders; std::shared_ptr pressedButton; + std::shared_ptr pressedSlider; std::string path; }; diff --git a/config/ui.json b/config/ui.json index 8127c68..c8753cf 100644 --- a/config/ui.json +++ b/config/ui.json @@ -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" + } } ] }