diff --git a/assets/textures/IconDesigner.png b/assets/textures/IconDesigner.png new file mode 100644 index 0000000..1bec0c2 Binary files /dev/null and b/assets/textures/IconDesigner.png differ diff --git a/modules/BgfxRenderer/Passes/SpritePass.cpp b/modules/BgfxRenderer/Passes/SpritePass.cpp index 162fee7..124791e 100644 --- a/modules/BgfxRenderer/Passes/SpritePass.cpp +++ b/modules/BgfxRenderer/Passes/SpritePass.cpp @@ -59,7 +59,7 @@ void SpritePass::setup(rhi::IRHIDevice& device) { // Create texture sampler uniform (must match shader: s_texColor) m_textureSampler = device.createUniform("s_texColor", 1); - // Create default white 4x4 texture (used when no texture is bound) + // Create default white 4x4 texture (restored to white) // Some drivers have issues with 1x1 textures uint32_t whitePixels[16]; for (int i = 0; i < 16; ++i) whitePixels[i] = 0xFFFFFFFF; // RGBA white @@ -98,15 +98,14 @@ void SpritePass::flushBatch(rhi::IRHIDevice& device, rhi::RHICommandBuffer& cmd, void SpritePass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi::RHICommandBuffer& cmd) { if (frame.spriteCount == 0) return; - // Set render state ONCE (like TextPass does) + // Prepare render state (will be set before each batch) rhi::RenderState state; state.blend = rhi::BlendMode::Alpha; state.cull = rhi::CullMode::None; state.depthTest = false; state.depthWrite = false; - cmd.setState(state); - // Sort sprites by layer for correct draw order + // Sort sprites by layer first (for correct draw order), then by texture (for batching) m_sortedIndices.clear(); m_sortedIndices.reserve(frame.spriteCount); for (size_t i = 0; i < frame.spriteCount; ++i) { @@ -114,27 +113,133 @@ void SpritePass::execute(const FramePacket& frame, rhi::IRHIDevice& device, rhi: } std::sort(m_sortedIndices.begin(), m_sortedIndices.end(), [&frame](uint32_t a, uint32_t b) { - return frame.sprites[a].layer < frame.sprites[b].layer; + // Sort by layer first, then by textureId for batching + if (frame.sprites[a].layer != frame.sprites[b].layer) { + return frame.sprites[a].layer < frame.sprites[b].layer; + } + return frame.sprites[a].textureId < frame.sprites[b].textureId; }); - // Copy sorted sprites to temporary buffer (like TextPass does with glyphs) - std::vector sortedSprites; - sortedSprites.reserve(frame.spriteCount); - for (uint32_t idx : m_sortedIndices) { - sortedSprites.push_back(frame.sprites[idx]); + // Batch sprites by texture + std::vector batchSprites; + batchSprites.reserve(frame.spriteCount); + + uint16_t currentTextureId = 0; + bool firstBatch = true; + + static int spriteLogCount = 0; + for (size_t i = 0; i < m_sortedIndices.size(); ++i) { + uint32_t idx = m_sortedIndices[i]; + const SpriteInstance& sprite = frame.sprites[idx]; + uint16_t spriteTexId = static_cast(sprite.textureId); + + // Log first few textured sprites + if (spriteLogCount < 10 && spriteTexId > 0) { + spdlog::info("🎨 [SpritePass] Processing sprite #{}: textureId={}, pos=({:.1f},{:.1f}), scale={}x{}, layer={}", + spriteLogCount++, spriteTexId, sprite.x, sprite.y, sprite.scaleX, sprite.scaleY, (int)sprite.layer); + } + + // Start new batch if texture changes + if (!firstBatch && spriteTexId != currentTextureId) { + // Flush previous batch using TRANSIENT BUFFER (one per batch) + uint32_t batchSize = static_cast(batchSprites.size()); + rhi::TransientInstanceBuffer transientBuffer = device.allocTransientInstanceBuffer(batchSize); + + // CRITICAL: Set render state before EACH batch (consumed by submit) + cmd.setState(state); + + // Get texture handle from ResourceCache + rhi::TextureHandle texHandle = m_defaultTexture; + if (m_resourceCache && currentTextureId > 0) { + auto cachedTex = m_resourceCache->getTextureById(currentTextureId); + if (cachedTex.isValid()) { + texHandle = cachedTex; + static int batchNum = 0; + spdlog::info("[Batch #{}] SpritePass flushing batch: textureId={}, handle={}, size={}", + batchNum++, currentTextureId, texHandle.id, batchSprites.size()); + } + } + + if (transientBuffer.isValid()) { + // Copy sprite data to transient buffer (frame-local, won't be overwritten) + std::memcpy(transientBuffer.data, batchSprites.data(), batchSize * sizeof(SpriteInstance)); + + cmd.setVertexBuffer(m_quadVB); + cmd.setIndexBuffer(m_quadIB); + cmd.setTransientInstanceBuffer(transientBuffer, 0, batchSize); + cmd.setTexture(0, texHandle, m_textureSampler); + cmd.drawInstanced(6, batchSize); + cmd.submit(0, m_shader, 0); + } else { + // Fallback to dynamic buffer (single batch limitation - data will be overwritten!) + device.updateBuffer(m_instanceBuffer, batchSprites.data(), batchSize * sizeof(SpriteInstance)); + + cmd.setVertexBuffer(m_quadVB); + cmd.setIndexBuffer(m_quadIB); + cmd.setInstanceBuffer(m_instanceBuffer, 0, batchSize); + cmd.setTexture(0, texHandle, m_textureSampler); + cmd.drawInstanced(6, batchSize); + cmd.submit(0, m_shader, 0); + } + + // Start new batch + batchSprites.clear(); + } + + batchSprites.push_back(sprite); + currentTextureId = spriteTexId; + firstBatch = false; } - // Update dynamic instance buffer with ALL sprites (like TextPass) - device.updateBuffer(m_instanceBuffer, sortedSprites.data(), - static_cast(sortedSprites.size() * sizeof(SpriteInstance))); + // Flush final batch + if (!batchSprites.empty()) { + // Use TRANSIENT BUFFER for final batch too + uint32_t batchSize = static_cast(batchSprites.size()); + rhi::TransientInstanceBuffer transientBuffer = device.allocTransientInstanceBuffer(batchSize); - // Set buffers and draw ALL sprites in ONE call (like TextPass) - cmd.setVertexBuffer(m_quadVB); - cmd.setIndexBuffer(m_quadIB); - cmd.setInstanceBuffer(m_instanceBuffer, 0, static_cast(sortedSprites.size())); - cmd.setTexture(0, m_defaultTexture, m_textureSampler); - cmd.drawInstanced(6, static_cast(sortedSprites.size())); - cmd.submit(0, m_shader, 0); + // CRITICAL: Set render state before EACH batch (consumed by submit) + cmd.setState(state); + + // Get texture handle from ResourceCache + rhi::TextureHandle texHandle = m_defaultTexture; + if (m_resourceCache && currentTextureId > 0) { + auto cachedTex = m_resourceCache->getTextureById(currentTextureId); + if (cachedTex.isValid()) { + texHandle = cachedTex; + static int finalBatchNum = 0; + spdlog::info("[Final Batch #{}] SpritePass flushing final batch: textureId={}, handle={}, size={}", + finalBatchNum++, currentTextureId, texHandle.id, batchSprites.size()); + } else { + static bool warnLogged = false; + if (!warnLogged) { + spdlog::warn("SpritePass: Texture ID {} not found in cache, using default", currentTextureId); + warnLogged = true; + } + } + } + + if (transientBuffer.isValid()) { + // Copy sprite data to transient buffer (frame-local, won't be overwritten) + std::memcpy(transientBuffer.data, batchSprites.data(), batchSize * sizeof(SpriteInstance)); + + cmd.setVertexBuffer(m_quadVB); + cmd.setIndexBuffer(m_quadIB); + cmd.setTransientInstanceBuffer(transientBuffer, 0, batchSize); + cmd.setTexture(0, texHandle, m_textureSampler); + cmd.drawInstanced(6, batchSize); + cmd.submit(0, m_shader, 0); + } else { + // Fallback to dynamic buffer (single batch limitation - data will be overwritten!) + device.updateBuffer(m_instanceBuffer, batchSprites.data(), batchSize * sizeof(SpriteInstance)); + + cmd.setVertexBuffer(m_quadVB); + cmd.setIndexBuffer(m_quadIB); + cmd.setInstanceBuffer(m_instanceBuffer, 0, batchSize); + cmd.setTexture(0, texHandle, m_textureSampler); + cmd.drawInstanced(6, batchSize); + cmd.submit(0, m_shader, 0); + } + } } } // namespace grove diff --git a/modules/BgfxRenderer/Scene/SceneCollector.cpp b/modules/BgfxRenderer/Scene/SceneCollector.cpp index 2eea1bf..485646a 100644 --- a/modules/BgfxRenderer/Scene/SceneCollector.cpp +++ b/modules/BgfxRenderer/Scene/SceneCollector.cpp @@ -28,9 +28,11 @@ void SceneCollector::collect(IIO* io, float deltaTime) { // Route message based on topic // Retained mode (new) - sprites if (msg.topic == "render:sprite:add") { + spdlog::info("βœ… RETAINED MODE: render:sprite:add received"); parseSpriteAdd(*msg.data); } else if (msg.topic == "render:sprite:update") { + spdlog::info("βœ… RETAINED MODE: render:sprite:update received"); parseSpriteUpdate(*msg.data); } else if (msg.topic == "render:sprite:remove") { @@ -48,6 +50,7 @@ void SceneCollector::collect(IIO* io, float deltaTime) { } // Ephemeral mode (legacy) else if (msg.topic == "render:sprite") { + spdlog::info("⚠️ EPHEMERAL MODE: render:sprite received (should not happen in retained mode!)"); parseSprite(*msg.data); } else if (msg.topic == "render:sprite:batch") { @@ -487,6 +490,9 @@ void SceneCollector::parseSpriteAdd(const IDataNode& data) { sprite.a = static_cast(color & 0xFF) / 255.0f; m_retainedSprites[renderId] = sprite; + spdlog::info("πŸ“₯ [SceneCollector] Stored SPRITE renderId={}, pos=({:.1f},{:.1f}), scale={}x{}, textureId={}, layer={}, color=({:.2f},{:.2f},{:.2f},{:.2f})", + renderId, sprite.x, sprite.y, sprite.scaleX, sprite.scaleY, (int)sprite.textureId, (int)sprite.layer, + sprite.r, sprite.g, sprite.b, sprite.a); } void SceneCollector::parseSpriteUpdate(const IDataNode& data) { diff --git a/modules/UIModule/Rendering/UIRenderer.cpp b/modules/UIModule/Rendering/UIRenderer.cpp index 76bdd65..c51d391 100644 --- a/modules/UIModule/Rendering/UIRenderer.cpp +++ b/modules/UIModule/Rendering/UIRenderer.cpp @@ -155,12 +155,20 @@ bool UIRenderer::updateSprite(uint32_t renderId, float x, float y, float w, floa } void UIRenderer::publishSpriteAdd(uint32_t renderId, float x, float y, float w, float h, int textureId, uint32_t color, int layer) { + spdlog::info("πŸ“€ [UIRenderer] Publishing render:sprite:add - renderId={}, center=({:.1f},{:.1f}), scale={}x{}, textureId={}, layer={}", + renderId, x + w * 0.5f, y + h * 0.5f, w, h, textureId, layer); + auto sprite = std::make_unique("sprite"); sprite->setInt("renderId", static_cast(renderId)); sprite->setDouble("x", static_cast(x + w * 0.5f)); sprite->setDouble("y", static_cast(y + h * 0.5f)); sprite->setDouble("scaleX", static_cast(w)); sprite->setDouble("scaleY", static_cast(h)); + sprite->setDouble("rotation", 0.0); + sprite->setDouble("u0", 0.0); + sprite->setDouble("v0", 0.0); + sprite->setDouble("u1", 1.0); + sprite->setDouble("v1", 1.0); sprite->setInt("color", static_cast(color)); sprite->setInt("textureId", textureId); sprite->setInt("layer", layer); @@ -174,6 +182,11 @@ void UIRenderer::publishSpriteUpdate(uint32_t renderId, float x, float y, float sprite->setDouble("y", static_cast(y + h * 0.5f)); sprite->setDouble("scaleX", static_cast(w)); sprite->setDouble("scaleY", static_cast(h)); + sprite->setDouble("rotation", 0.0); + sprite->setDouble("u0", 0.0); + sprite->setDouble("v0", 0.0); + sprite->setDouble("u1", 1.0); + sprite->setDouble("v1", 1.0); sprite->setInt("color", static_cast(color)); sprite->setInt("textureId", textureId); sprite->setInt("layer", layer); diff --git a/modules/UIModule/Widgets/UIButton.cpp b/modules/UIModule/Widgets/UIButton.cpp index 162da66..7a9a8d8 100644 --- a/modules/UIModule/Widgets/UIButton.cpp +++ b/modules/UIModule/Widgets/UIButton.cpp @@ -44,11 +44,21 @@ void UIButton::render(UIRenderer& renderer) { const ButtonStyle& style = getCurrentStyle(); + static int logCount = 0; + if (logCount < 10) { // Log first 10 buttons to see all textured ones + spdlog::info("UIButton[{}]::render() id='{}', state={}, normalStyle.textureId={}, useTexture={}", + logCount, id, (int)state, normalStyle.textureId, normalStyle.useTexture); + spdlog::info(" current style: textureId={}, useTexture={}", style.textureId, style.useTexture); + logCount++; + } + // Retained mode: only publish if changed int bgLayer = renderer.nextLayer(); // Render background (texture or solid color) if (style.useTexture && style.textureId > 0) { + spdlog::info("🎨 [UIButton '{}'] Rendering SPRITE: renderId={}, pos=({},{}), size={}x{}, textureId={}, color=0x{:08X}, layer={}", + id, m_renderId, absX, absY, width, height, style.textureId, style.bgColor, bgLayer); renderer.updateSprite(m_renderId, absX, absY, width, height, style.textureId, style.bgColor, bgLayer); } else { renderer.updateRect(m_renderId, absX, absY, width, height, style.bgColor, bgLayer); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2faccfe..b4c414d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1451,3 +1451,180 @@ if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND GROVE_BUILD_UI_MODULE AND SDL2_AVAILA ) message(STATUS "Single button test 'test_single_button' enabled") endif() + +# UI Texture Support Test - headless test demonstrating texture properties +if(GROVE_BUILD_UI_MODULE) + add_executable(test_ui_textures + visual/test_ui_textures.cpp + ) + target_include_directories(test_ui_textures PRIVATE + ${CMAKE_SOURCE_DIR}/modules + ${CMAKE_SOURCE_DIR}/modules/UIModule + ) + target_link_libraries(test_ui_textures PRIVATE + GroveEngine::impl + UIModule_static + spdlog::spdlog + ) + message(STATUS "UI texture support test 'test_ui_textures' enabled") +endif() + +# Textured UI Visual Demo - shows widgets with custom textures +if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND GROVE_BUILD_UI_MODULE AND SDL2_AVAILABLE) + add_executable(test_ui_textured_demo WIN32 + visual/test_ui_textured_demo.cpp + ) + target_include_directories(test_ui_textured_demo PRIVATE + ${CMAKE_SOURCE_DIR}/modules + ${CMAKE_SOURCE_DIR}/modules/BgfxRenderer + ${CMAKE_SOURCE_DIR}/modules/UIModule + ) + target_link_libraries(test_ui_textured_demo PRIVATE + SDL2::SDL2main + SDL2::SDL2 + GroveEngine::impl + BgfxRenderer_static + UIModule_static + spdlog::spdlog + ) + message(STATUS "Textured UI demo 'test_ui_textured_demo' enabled") +endif() + +# Simple textured UI demo - shows widget properties (no rendering) +if(GROVE_BUILD_UI_MODULE) + add_executable(test_ui_textured_simple + visual/test_ui_textured_simple.cpp + ) + target_include_directories(test_ui_textured_simple PRIVATE + ${CMAKE_SOURCE_DIR}/modules + ${CMAKE_SOURCE_DIR}/modules/UIModule + ) + target_link_libraries(test_ui_textured_simple PRIVATE + GroveEngine::impl + UIModule_static + spdlog::spdlog + ) + message(STATUS "Simple textured UI demo 'test_ui_textured_simple' enabled") +endif() + +# Textured Button Visual Test - shows REAL textures on buttons +if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND GROVE_BUILD_UI_MODULE AND SDL2_AVAILABLE) + add_executable(test_textured_button WIN32 + visual/test_textured_button.cpp + ) + target_include_directories(test_textured_button PRIVATE + ${CMAKE_SOURCE_DIR}/modules + ${CMAKE_SOURCE_DIR}/modules/BgfxRenderer + ${CMAKE_SOURCE_DIR}/modules/UIModule + ) + target_link_libraries(test_textured_button PRIVATE + SDL2::SDL2main + SDL2::SDL2 + GroveEngine::impl + BgfxRenderer_static + UIModule_static + spdlog::spdlog + ) + message(STATUS "Textured button test 'test_textured_button' enabled") +endif() + +# Minimal Textured Demo - Direct sprite rendering with textures +if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND SDL2_AVAILABLE) + add_executable(test_textured_demo_minimal WIN32 + visual/test_textured_demo_minimal.cpp + ) + target_include_directories(test_textured_demo_minimal PRIVATE + ${CMAKE_SOURCE_DIR}/modules + ${CMAKE_SOURCE_DIR}/modules/BgfxRenderer + ) + target_link_libraries(test_textured_demo_minimal PRIVATE + SDL2::SDL2main + SDL2::SDL2 + GroveEngine::impl + BgfxRenderer_static + spdlog::spdlog + ) + message(STATUS "Minimal textured demo 'test_textured_demo_minimal' enabled") +endif() + +# Button with PNG texture - Load real PNG file and apply to button +if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND GROVE_BUILD_UI_MODULE AND SDL2_AVAILABLE) + add_executable(test_button_with_png WIN32 + visual/test_button_with_png.cpp + ) + target_include_directories(test_button_with_png PRIVATE + ${CMAKE_SOURCE_DIR}/modules + ${CMAKE_SOURCE_DIR}/modules/BgfxRenderer + ${CMAKE_SOURCE_DIR}/modules/UIModule + ) + target_link_libraries(test_button_with_png PRIVATE + SDL2::SDL2main + SDL2::SDL2 + GroveEngine::impl + BgfxRenderer_static + UIModule_static + spdlog::spdlog + ) + message(STATUS "PNG button test 'test_button_with_png' enabled") +endif() + +# 3 Buttons Minimal Test - 3 textured buttons in minimal layout +if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND GROVE_BUILD_UI_MODULE AND SDL2_AVAILABLE) + add_executable(test_3buttons_minimal WIN32 + visual/test_3buttons_minimal.cpp + ) + target_include_directories(test_3buttons_minimal PRIVATE + ${CMAKE_SOURCE_DIR}/modules + ${CMAKE_SOURCE_DIR}/modules/BgfxRenderer + ${CMAKE_SOURCE_DIR}/modules/UIModule + ) + target_link_libraries(test_3buttons_minimal PRIVATE + SDL2::SDL2main + SDL2::SDL2 + GroveEngine::impl + BgfxRenderer_static + UIModule_static + spdlog::spdlog + ) + message(STATUS "3 buttons minimal test 'test_3buttons_minimal' enabled") +endif() + +# 1 Button Texture 2 Test - diagnostic to see if only texture 1 works +if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND GROVE_BUILD_UI_MODULE AND SDL2_AVAILABLE) + add_executable(test_1button_texture2 WIN32 + visual/test_1button_texture2.cpp + ) + target_include_directories(test_1button_texture2 PRIVATE + ${CMAKE_SOURCE_DIR}/modules + ${CMAKE_SOURCE_DIR}/modules/BgfxRenderer + ${CMAKE_SOURCE_DIR}/modules/UIModule + ) + target_link_libraries(test_1button_texture2 PRIVATE + SDL2::SDL2main + SDL2::SDL2 + GroveEngine::impl + BgfxRenderer_static + UIModule_static + spdlog::spdlog + ) + message(STATUS "1 button texture 2 test 'test_1button_texture2' enabled") +endif() + +# Direct sprite texture test - bypasses UIModule, uses renderer directly +if(WIN32 AND GROVE_BUILD_BGFX_RENDERER AND SDL2_AVAILABLE) + add_executable(test_direct_sprite_texture WIN32 + visual/test_direct_sprite_texture.cpp + ) + target_include_directories(test_direct_sprite_texture PRIVATE + ${CMAKE_SOURCE_DIR}/modules + ${CMAKE_SOURCE_DIR}/modules/BgfxRenderer + ) + target_link_libraries(test_direct_sprite_texture PRIVATE + SDL2::SDL2main + SDL2::SDL2 + GroveEngine::impl + BgfxRenderer_static + spdlog::spdlog + ) + message(STATUS "Direct sprite texture test 'test_direct_sprite_texture' enabled") +endif() diff --git a/tests/visual/test_3buttons_minimal.cpp b/tests/visual/test_3buttons_minimal.cpp new file mode 100644 index 0000000..3719432 --- /dev/null +++ b/tests/visual/test_3buttons_minimal.cpp @@ -0,0 +1,211 @@ +/** + * Test: UIButton avec texture PNG chargΓ©e depuis un fichier + * Ce test montre qu'on peut mettre une VRAIE image sur un bouton + */ + +#include +#include +#include +#include +#include + +#include "BgfxRendererModule.h" +#include "UIModule/UIModule.h" +#include "../modules/BgfxRenderer/Resources/ResourceCache.h" +#include "../modules/BgfxRenderer/RHI/RHITypes.h" +#include "../modules/BgfxRenderer/RHI/RHIDevice.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace grove; + +int main(int argc, char* argv[]) { + spdlog::set_level(spdlog::level::info); + auto logger = spdlog::stdout_color_mt("TexturedButtonTest"); + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + std::cerr << "SDL_Init failed" << std::endl; + return 1; + } + + SDL_Window* window = SDL_CreateWindow( + "Textured Button Test - Gradient", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 800, 600, SDL_WINDOW_SHOWN + ); + + SDL_SysWMinfo wmi; + SDL_VERSION(&wmi.version); + SDL_GetWindowWMInfo(window, &wmi); + + // Create IIO instances - IMPORTANT: game publishes input, ui subscribes and publishes render commands + auto gameIO = IntraIOManager::getInstance().createInstance("game"); + auto uiIO = IntraIOManager::getInstance().createInstance("ui"); + auto rendererIO = IntraIOManager::getInstance().createInstance("renderer"); + + gameIO->subscribe("ui:hover"); + gameIO->subscribe("ui:click"); + gameIO->subscribe("ui:action"); + + // Initialize BgfxRenderer WITH 3 TEXTURES loaded via config + auto renderer = std::make_unique(); + { + JsonDataNode config("config"); + config.setDouble("nativeWindowHandle", + static_cast(reinterpret_cast(wmi.info.win.window))); + config.setInt("windowWidth", 800); + config.setInt("windowHeight", 600); + // Load 3 textures + config.setString("texture1", "../../assets/textures/5oxaxt1vo2f91.jpg"); // Car + config.setString("texture2", "../../assets/textures/1f440.png"); // Eyes + config.setString("texture3", "../../assets/textures/IconDesigner.png"); // Icon + + renderer->setConfiguration(config, rendererIO.get(), nullptr); + } + + logger->info("βœ“ Loaded 3 textures (IDs: 1, 2, 3)"); + + // Initialize UIModule with 3 TEXTURED BUTTONS + auto ui = std::make_unique(); + { + JsonDataNode config("config"); + config.setInt("windowWidth", 800); + config.setInt("windowHeight", 600); + + nlohmann::json layoutJson = { + {"id", "root"}, + {"type", "panel"}, + {"x", 0}, {"y", 0}, + {"width", 800}, {"height", 600}, // Full screen invisible panel (just container) + {"style", { + {"bgColor", "0x00000000"} // Fully transparent - just a container + }}, + {"children", { + { + {"id", "btn_car"}, + {"type", "button"}, + {"x", 50}, + {"y", 50}, + {"width", 400}, + {"height", 200}, + {"text", ""}, + {"onClick", "car_action"}, + {"style", { + {"normal", {{"textureId", 1}, {"bgColor", "0xFFFFFFFF"}}}, + {"hover", {{"textureId", 1}, {"bgColor", "0xFFFF00FF"}}}, + {"pressed", {{"textureId", 1}, {"bgColor", "0x888888FF"}}} + }} + }, + { + {"id", "btn_eyes"}, + {"type", "button"}, + {"x", 50}, + {"y", 270}, + {"width", 250}, + {"height", 200}, + {"text", ""}, + {"onClick", "eyes_action"}, + {"style", { + {"normal", {{"textureId", 2}, {"bgColor", "0xFFFFFFFF"}}}, + {"hover", {{"textureId", 2}, {"bgColor", "0x00FFFFFF"}}}, + {"pressed", {{"textureId", 2}, {"bgColor", "0x888888FF"}}} + }} + }, + { + {"id", "btn_icon"}, + {"type", "button"}, + {"x", 320}, + {"y", 270}, + {"width", 250}, + {"height", 200}, + {"text", ""}, + {"onClick", "icon_action"}, + {"style", { + {"normal", {{"textureId", 3}, {"bgColor", "0xFFFFFFFF"}}}, + {"hover", {{"textureId", 3}, {"bgColor", "0xFF00FFFF"}}}, + {"pressed", {{"textureId", 3}, {"bgColor", "0x888888FF"}}} + }} + } + }} + }; + + auto layoutNode = std::make_unique("layout", layoutJson); + config.setChild("layout", std::move(layoutNode)); + + ui->setConfiguration(config, uiIO.get(), nullptr); + logger->info("βœ“ UIModule configured with 3 textured buttons!"); + } + + logger->info("\n╔════════════════════════════════════════╗"); + logger->info("β•‘ 3 BOUTONS AVEC TEXTURES β•‘"); + logger->info("╠════════════════════════════════════════╣"); + logger->info("β•‘ Button 1: Car (textureId=1) β•‘"); + logger->info("β•‘ Button 2: Eyes (textureId=2) β•‘"); + logger->info("β•‘ Button 3: Icon (textureId=3) β•‘"); + logger->info("β•‘ Press ESC to exit β•‘"); + logger->info("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n"); + + bool running = true; + while (running) { + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT || + (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) { + running = false; + } + + // Forward mouse events + if (e.type == SDL_MOUSEMOTION) { + auto mouseMsg = std::make_unique("mouse"); + mouseMsg->setDouble("x", static_cast(e.motion.x)); + mouseMsg->setDouble("y", static_cast(e.motion.y)); + gameIO->publish("input:mouse:move", std::move(mouseMsg)); + } + else if (e.type == SDL_MOUSEBUTTONDOWN || e.type == SDL_MOUSEBUTTONUP) { + auto mouseMsg = std::make_unique("mouse"); + mouseMsg->setInt("button", e.button.button); + mouseMsg->setBool("pressed", e.type == SDL_MOUSEBUTTONDOWN); + mouseMsg->setDouble("x", static_cast(e.button.x)); + mouseMsg->setDouble("y", static_cast(e.button.y)); + gameIO->publish("input:mouse:button", std::move(mouseMsg)); + } + } + + // Check for UI events + while (gameIO->hasMessages() > 0) { + auto msg = gameIO->pullMessage(); + if (msg.topic == "ui:action") { + logger->info("πŸ–±οΈ BOUTON CLICKΓ‰!"); + } + } + + // Update modules + JsonDataNode input("input"); + input.setDouble("deltaTime", 0.016); + ui->process(input); + renderer->process(input); + + SDL_Delay(16); + } + + logger->info("Cleaning up..."); + + // Textures are managed by ResourceCache, will be cleaned up in renderer->shutdown() + ui->shutdown(); + renderer->shutdown(); + + IntraIOManager::getInstance().removeInstance("game"); + IntraIOManager::getInstance().removeInstance("ui"); + IntraIOManager::getInstance().removeInstance("renderer"); + + SDL_DestroyWindow(window); + SDL_Quit(); + + logger->info("Test complete!"); + return 0; +} diff --git a/tests/visual/test_ui_showcase.cpp b/tests/visual/test_ui_showcase.cpp index 19686c7..73940fb 100644 --- a/tests/visual/test_ui_showcase.cpp +++ b/tests/visual/test_ui_showcase.cpp @@ -37,20 +37,20 @@ using namespace grove; -// Create the UI layout JSON - must be a single root widget with children +// Create the UI layout JSON - MINIMAL VERSION WITH ONLY TEXTURED BUTTONS static nlohmann::json createUILayout() { nlohmann::json root; - // Root panel (full screen background) + // Root panel (TRANSPARENT like test_button_with_png!) root["type"] = "panel"; root["id"] = "root"; root["x"] = 0; root["y"] = 0; root["width"] = 1024; root["height"] = 768; - root["style"] = {{"bgColor", "0x1a1a2eFF"}}; + root["style"] = {{"bgColor", "0x00000000"}}; // TRANSPARENT! - // Children array (like test_single_button) + // Children array - ONLY TEXTURED BUTTONS nlohmann::json children = nlohmann::json::array(); // Title label @@ -151,6 +151,78 @@ static nlohmann::json createUILayout() { }} }); + // === TEXTURED BUTTONS PANEL === (TRANSPARENT FOR TESTING) + children.push_back({ + {"type", "panel"}, + {"id", "textured_buttons_panel"}, + {"x", 350}, {"y", 120}, + {"width", 300}, {"height", 280}, + {"style", {{"bgColor", "0x00000000"}}} // TRANSPARENT! + }); + + children.push_back({ + {"type", "label"}, + {"id", "textured_buttons_title"}, + {"x", 365}, {"y", 130}, + {"width", 250}, {"height", 30}, + {"text", "Sprite Buttons"}, + {"style", {{"fontSize", 20}, {"color", "0xFFFFFFFF"}}} + }); + + // Textured Button 1 - Car (HUGE LIKE WORKING TEST!) + children.push_back({ + {"type", "button"}, + {"id", "btn_car"}, + {"x", 50}, {"y", 120}, + {"width", 400}, {"height", 200}, // Same as test_button_with_png! + {"text", ""}, + {"onClick", "sprite_car"}, + {"style", { + {"normal", {{"textureId", 1}, {"bgColor", "0xFFFFFFFF"}, {"textColor", "0x000000FF"}}}, + {"hover", {{"textureId", 1}, {"bgColor", "0xFFFF00FF"}, {"textColor", "0x000000FF"}}}, // Yellow tint + {"pressed", {{"textureId", 1}, {"bgColor", "0x888888FF"}, {"textColor", "0x000000FF"}}} // Dark tint + }} + }); + + // Textured Button 2 - Eyes (HUGE!) + children.push_back({ + {"type", "button"}, + {"id", "btn_eyes"}, + {"x", 470}, {"y", 120}, + {"width", 250}, {"height", 200}, // Much bigger! + {"text", ""}, + {"onClick", "sprite_eyes"}, + {"style", { + {"normal", {{"textureId", 2}, {"bgColor", "0xFFFFFFFF"}, {"textColor", "0x000000FF"}}}, + {"hover", {{"textureId", 2}, {"bgColor", "0x00FFFFFF"}, {"textColor", "0x000000FF"}}}, // Cyan tint + {"pressed", {{"textureId", 2}, {"bgColor", "0x888888FF"}, {"textColor", "0x000000FF"}}} + }} + }); + + // Textured Button 3 - Icon (HUGE!) + children.push_back({ + {"type", "button"}, + {"id", "btn_icon"}, + {"x", 50}, {"y", 340}, + {"width", 250}, {"height", 200}, // Much bigger! + {"text", ""}, + {"onClick", "sprite_icon"}, + {"style", { + {"normal", {{"textureId", 3}, {"bgColor", "0xFFFFFFFF"}, {"textColor", "0x000000FF"}}}, + {"hover", {{"textureId", 3}, {"bgColor", "0xFF00FFFF"}, {"textColor", "0x000000FF"}}}, // Magenta tint + {"pressed", {{"textureId", 3}, {"bgColor", "0x888888FF"}, {"textColor", "0x000000FF"}}} + }} + }); + + // Info label for textured buttons + children.push_back({ + {"type", "label"}, + {"x", 370}, {"y", 340}, + {"width", 260}, {"height", 50}, + {"text", "Retained mode:\nTextures only sent once!"}, + {"style", {{"fontSize", 12}, {"color", "0xAAAAAAFF"}}} + }); + // === MIDDLE COLUMN: Inputs Panel === children.push_back({ {"type", "panel"}, @@ -351,7 +423,7 @@ public: m_inputIO = m_inputIOPtr.get(); m_gameIO = m_gameIOPtr.get(); - // Create and configure BgfxRenderer + // Create and configure BgfxRenderer with textures m_renderer = std::make_unique(); { JsonDataNode config("config"); @@ -359,10 +431,15 @@ public: static_cast(reinterpret_cast(wmi.info.win.window))); config.setInt("windowWidth", 1024); config.setInt("windowHeight", 768); - config.setString("backend", "d3d11"); + // config.setString("backend", "d3d11"); // LET BGFX CHOOSE LIKE test_button_with_png! config.setBool("vsync", true); + // Load textures for sprite buttons + config.setString("texture1", "../../assets/textures/5oxaxt1vo2f91.jpg"); // Car + config.setString("texture2", "../../assets/textures/1f440.png"); // Eyes emoji + config.setString("texture3", "../../assets/textures/IconDesigner.png"); // Icon m_renderer->setConfiguration(config, m_rendererIO, nullptr); } + m_logger->info("βœ“ Loaded 3 textures for sprite buttons (IDs: 1, 2, 3)"); // Create and configure UIModule with inline layout m_uiModule = std::make_unique(); @@ -500,6 +577,15 @@ private: else if (action == "action_danger") { m_logger->warn("Danger button clicked!"); } + else if (action == "sprite_car") { + m_logger->info("πŸš— Car sprite button clicked! (Texture ID: 1)"); + } + else if (action == "sprite_eyes") { + m_logger->info("πŸ‘€ Eyes sprite button clicked! (Texture ID: 2)"); + } + else if (action == "sprite_icon") { + m_logger->info("🎨 Icon sprite button clicked! (Texture ID: 3)"); + } } else if (msg.topic == "ui:click") { std::string widgetId = msg.data->getString("widgetId", "");