diff --git a/.gitignore b/.gitignore index 0733a38..89eb229 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,5 @@ desktop.ini *.tmp *.swp *~ -nul +nul +tmpclaude-* diff --git a/modules/BgfxRenderer/BgfxRendererModule.cpp b/modules/BgfxRenderer/BgfxRendererModule.cpp index f3c9bd4..0cee8e6 100644 --- a/modules/BgfxRenderer/BgfxRendererModule.cpp +++ b/modules/BgfxRenderer/BgfxRendererModule.cpp @@ -23,6 +23,14 @@ namespace grove { BgfxRendererModule::BgfxRendererModule() = default; BgfxRendererModule::~BgfxRendererModule() = default; +ResourceCache* BgfxRendererModule::getResourceCache() const { + return m_resourceCache.get(); +} + +rhi::IRHIDevice* BgfxRendererModule::getDevice() const { + return m_device.get(); +} + void BgfxRendererModule::setConfiguration(const IDataNode& config, IIO* io, ITaskScheduler* scheduler) { m_io = io; diff --git a/modules/BgfxRenderer/BgfxRendererModule.h b/modules/BgfxRenderer/BgfxRendererModule.h index 64b4aa0..4de28bd 100644 --- a/modules/BgfxRenderer/BgfxRendererModule.h +++ b/modules/BgfxRenderer/BgfxRendererModule.h @@ -45,6 +45,13 @@ public: std::string getType() const override { return "bgfx_renderer"; } bool isIdle() const override { return true; } + // ======================================== + // Public API for external access + // ======================================== + + ResourceCache* getResourceCache() const; + rhi::IRHIDevice* getDevice() const; + private: // Logger std::shared_ptr m_logger; diff --git a/modules/BgfxRenderer/RHI/BgfxDevice.cpp b/modules/BgfxRenderer/RHI/BgfxDevice.cpp index f41ccac..ec1ac2e 100644 --- a/modules/BgfxRenderer/RHI/BgfxDevice.cpp +++ b/modules/BgfxRenderer/RHI/BgfxDevice.cpp @@ -205,9 +205,13 @@ public: } UniformHandle createUniform(const char* name, uint8_t numVec4s) override { + // Detect sampler uniforms by name prefix (bgfx convention: s_*) + bool isSampler = (name[0] == 's' && name[1] == '_'); + bgfx::UniformHandle uniform = bgfx::createUniform( name, - numVec4s == 1 ? bgfx::UniformType::Vec4 : bgfx::UniformType::Mat4 + isSampler ? bgfx::UniformType::Sampler : + (numVec4s == 1 ? bgfx::UniformType::Vec4 : bgfx::UniformType::Mat4) ); UniformHandle result; @@ -557,6 +561,15 @@ public: if (hasTexture) { bgfx::TextureHandle tex = { pendingTexture.id }; bgfx::UniformHandle sampler = { pendingSampler.id }; + + static int submitCount = 0; + if (submitCount < 10) { + spdlog::info("[Submit #{}] BgfxDevice::submit() - pendingTexture.id={}, tex.idx={}, sampler.idx={}, slot={}", + submitCount, pendingTexture.id, tex.idx, sampler.idx, pendingTextureSlot); + spdlog::info(" bgfx::isValid(tex): {}", bgfx::isValid(tex)); + submitCount++; + } + bgfx::setTexture(pendingTextureSlot, sampler, tex); } bgfx::ProgramHandle program = { cmd.submit.shader.id }; diff --git a/modules/BgfxRenderer/Resources/ResourceCache.h b/modules/BgfxRenderer/Resources/ResourceCache.h index 2cea40f..f0e6f97 100644 --- a/modules/BgfxRenderer/Resources/ResourceCache.h +++ b/modules/BgfxRenderer/Resources/ResourceCache.h @@ -31,6 +31,10 @@ public: // Loading (called from main thread) - returns texture ID uint16_t loadTextureWithId(rhi::IRHIDevice& device, const std::string& path); + // Register an already-created texture (for runtime/procedural textures) + // Returns the assigned texture ID + uint16_t registerTexture(rhi::TextureHandle handle, const std::string& name = ""); + // Legacy loading (returns handle directly) rhi::TextureHandle loadTexture(rhi::IRHIDevice& device, const std::string& path); rhi::ShaderHandle loadShader(rhi::IRHIDevice& device, const std::string& name, diff --git a/modules/UIModule/Core/UITree.cpp b/modules/UIModule/Core/UITree.cpp index 641f1f1..ca2e86a 100644 --- a/modules/UIModule/Core/UITree.cpp +++ b/modules/UIModule/Core/UITree.cpp @@ -86,6 +86,9 @@ void UITree::registerDefaultWidgets() { style.borderRadius = static_cast(styleNode->getDouble("borderRadius", style.borderRadius)); style.textureId = styleNode->getInt("textureId", 0); style.useTexture = style.textureId > 0; + if (style.textureId > 0) { + spdlog::info("UIButton style parsed: textureId={}, useTexture={}", style.textureId, style.useTexture); + } }; // Parse style (const_cast safe for read-only operations) diff --git a/modules/UIModule/Widgets/UICheckbox.cpp b/modules/UIModule/Widgets/UICheckbox.cpp index db33fb0..d78f697 100644 --- a/modules/UIModule/Widgets/UICheckbox.cpp +++ b/modules/UIModule/Widgets/UICheckbox.cpp @@ -34,22 +34,42 @@ void UICheckbox::render(UIRenderer& renderer) { // Box background (retained mode) int boxLayer = renderer.nextLayer(); uint32_t currentBoxColor = isHovered ? 0x475569FF : boxColor; - renderer.updateRect(m_renderId, boxX, boxY, boxSize, boxSize, currentBoxColor, boxLayer); + + if (useBoxTexture && boxTextureId > 0) { + renderer.updateSprite(m_renderId, boxX, boxY, boxSize, boxSize, boxTextureId, boxTintColor, boxLayer); + } else { + renderer.updateRect(m_renderId, boxX, boxY, boxSize, boxSize, currentBoxColor, boxLayer); + } // Check mark if checked (retained mode) int checkLayer = renderer.nextLayer(); if (checked) { - // Draw a smaller filled rect as checkmark float checkPadding = boxSize * 0.25f; - renderer.updateRect( - m_checkRenderId, - boxX + checkPadding, - boxY + checkPadding, - boxSize - checkPadding * 2, - boxSize - checkPadding * 2, - checkColor, - checkLayer - ); + + if (useCheckTexture && checkTextureId > 0) { + // Draw checkmark texture + renderer.updateSprite( + m_checkRenderId, + boxX + checkPadding, + boxY + checkPadding, + boxSize - checkPadding * 2, + boxSize - checkPadding * 2, + checkTextureId, + checkTintColor, + checkLayer + ); + } else { + // Draw a smaller filled rect as checkmark + renderer.updateRect( + m_checkRenderId, + boxX + checkPadding, + boxY + checkPadding, + boxSize - checkPadding * 2, + boxSize - checkPadding * 2, + checkColor, + checkLayer + ); + } } else { // Hide checkmark when unchecked (zero-size rect) renderer.updateRect(m_checkRenderId, 0, 0, 0, 0, 0x00000000, checkLayer); diff --git a/modules/UIModule/Widgets/UICheckbox.h b/modules/UIModule/Widgets/UICheckbox.h index fc91fc1..64e9632 100644 --- a/modules/UIModule/Widgets/UICheckbox.h +++ b/modules/UIModule/Widgets/UICheckbox.h @@ -49,6 +49,15 @@ public: float fontSize = 16.0f; float spacing = 8.0f; // Space between box and text + // Texture support + int boxTextureId = 0; // Box texture ID (0 = solid color) + bool useBoxTexture = false; // Use texture for box + uint32_t boxTintColor = 0xFFFFFFFF; // Tint for box texture + + int checkTextureId = 0; // Checkmark texture ID (0 = solid rect) + bool useCheckTexture = false; // Use texture for checkmark + uint32_t checkTintColor = 0xFFFFFFFF; // Tint for checkmark texture + // State bool isHovered = false; bool isPressed = false; diff --git a/modules/UIModule/Widgets/UIPanel.cpp b/modules/UIModule/Widgets/UIPanel.cpp index 3fb9786..6f149e4 100644 --- a/modules/UIModule/Widgets/UIPanel.cpp +++ b/modules/UIModule/Widgets/UIPanel.cpp @@ -31,7 +31,16 @@ void UIPanel::render(UIRenderer& renderer) { // Retained mode: only publish if changed int layer = renderer.nextLayer(); - renderer.updateRect(m_renderId, absX, absY, width, height, bgColor, layer); + + // Check if fully transparent (alpha channel = 0) + bool isFullyTransparent = (bgColor & 0xFF) == 0; + + // Render background (texture or solid color) - skip if fully transparent + if (useTexture && textureId > 0) { + renderer.updateSprite(m_renderId, absX, absY, width, height, textureId, tintColor, layer); + } else if (!isFullyTransparent) { + renderer.updateRect(m_renderId, absX, absY, width, height, bgColor, layer); + } // Render children on top renderChildren(renderer); diff --git a/modules/UIModule/Widgets/UIPanel.h b/modules/UIModule/Widgets/UIPanel.h index 92a0cc0..573a6ad 100644 --- a/modules/UIModule/Widgets/UIPanel.h +++ b/modules/UIModule/Widgets/UIPanel.h @@ -25,6 +25,11 @@ public: float borderRadius = 0.0f; // For future use float borderWidth = 0.0f; uint32_t borderColor = 0x000000FF; + + // Texture support + int textureId = 0; // Texture ID (0 = solid color) + bool useTexture = false; // Use texture instead of solid color + uint32_t tintColor = 0xFFFFFFFF; // RGBA tint for texture (white = no tint) }; } // namespace grove diff --git a/modules/UIModule/Widgets/UIProgressBar.cpp b/modules/UIModule/Widgets/UIProgressBar.cpp index 4869d46..fe27f49 100644 --- a/modules/UIModule/Widgets/UIProgressBar.cpp +++ b/modules/UIModule/Widgets/UIProgressBar.cpp @@ -30,16 +30,28 @@ void UIProgressBar::render(UIRenderer& renderer) { // Retained mode: only publish if changed int bgLayer = renderer.nextLayer(); - renderer.updateRect(m_renderId, absX, absY, width, height, bgColor, bgLayer); + if (useBgTexture && bgTextureId > 0) { + renderer.updateSprite(m_renderId, absX, absY, width, height, bgTextureId, bgTintColor, bgLayer); + } else { + renderer.updateRect(m_renderId, absX, absY, width, height, bgColor, bgLayer); + } // Render fill based on progress int fillLayer = renderer.nextLayer(); if (horizontal) { float fillWidth = progress * width; - renderer.updateRect(m_fillRenderId, absX, absY, fillWidth, height, fillColor, fillLayer); + if (useFillTexture && fillTextureId > 0) { + renderer.updateSprite(m_fillRenderId, absX, absY, fillWidth, height, fillTextureId, fillTintColor, fillLayer); + } else { + renderer.updateRect(m_fillRenderId, absX, absY, fillWidth, height, fillColor, fillLayer); + } } else { float fillHeight = progress * height; - renderer.updateRect(m_fillRenderId, absX, absY + height - fillHeight, width, fillHeight, fillColor, fillLayer); + if (useFillTexture && fillTextureId > 0) { + renderer.updateSprite(m_fillRenderId, absX, absY + height - fillHeight, width, fillHeight, fillTextureId, fillTintColor, fillLayer); + } else { + renderer.updateRect(m_fillRenderId, absX, absY + height - fillHeight, width, fillHeight, fillColor, fillLayer); + } } // Render percentage text if enabled diff --git a/modules/UIModule/Widgets/UIProgressBar.h b/modules/UIModule/Widgets/UIProgressBar.h index 652e1b0..aed7182 100644 --- a/modules/UIModule/Widgets/UIProgressBar.h +++ b/modules/UIModule/Widgets/UIProgressBar.h @@ -42,6 +42,15 @@ public: uint32_t textColor = 0xFFFFFFFF; float fontSize = 14.0f; + // Texture support + int bgTextureId = 0; // Background texture ID (0 = solid color) + bool useBgTexture = false; // Use texture for background + uint32_t bgTintColor = 0xFFFFFFFF; // Tint for background texture + + int fillTextureId = 0; // Fill texture ID (0 = solid color) + bool useFillTexture = false; // Use texture for fill + uint32_t fillTintColor = 0xFFFFFFFF; // Tint for fill texture + private: // Retained mode render IDs uint32_t m_fillRenderId = 0; // Separate ID for fill bar element diff --git a/modules/UIModule/Widgets/UIScrollPanel.cpp b/modules/UIModule/Widgets/UIScrollPanel.cpp index 9bc8cb1..332ebc7 100644 --- a/modules/UIModule/Widgets/UIScrollPanel.cpp +++ b/modules/UIModule/Widgets/UIScrollPanel.cpp @@ -72,7 +72,11 @@ void UIScrollPanel::render(UIRenderer& renderer) { // Render background int bgLayer = renderer.nextLayer(); - renderer.updateRect(m_renderId, absX, absY, width, height, bgColor, bgLayer); + if (useBgTexture && bgTextureId > 0) { + renderer.updateSprite(m_renderId, absX, absY, width, height, bgTextureId, bgTintColor, bgLayer); + } else { + renderer.updateRect(m_renderId, absX, absY, width, height, bgColor, bgLayer); + } // Render border if needed if (borderWidth > 0.0f) { @@ -225,7 +229,12 @@ void UIScrollPanel::renderScrollbar(UIRenderer& renderer) { // Render scrollbar background track float trackX = absX + width - scrollbarWidth; int trackLayer = renderer.nextLayer(); - renderer.updateRect(m_scrollTrackId, trackX, absY, scrollbarWidth, height, scrollbarBgColor, trackLayer); + + if (useScrollbarTrackTexture && scrollbarTrackTextureId > 0) { + renderer.updateSprite(m_scrollTrackId, trackX, absY, scrollbarWidth, height, scrollbarTrackTextureId, scrollbarTrackTintColor, trackLayer); + } else { + renderer.updateRect(m_scrollTrackId, trackX, absY, scrollbarWidth, height, scrollbarBgColor, trackLayer); + } // Render scrollbar thumb float sbX, sbY, sbW, sbH; @@ -233,7 +242,12 @@ void UIScrollPanel::renderScrollbar(UIRenderer& renderer) { // Use hover color if hovered (would need ctx passed to render, simplified for now) int thumbLayer = renderer.nextLayer(); - renderer.updateRect(m_scrollThumbId, sbX, sbY, sbW, sbH, scrollbarColor, thumbLayer); + + if (useScrollbarThumbTexture && scrollbarThumbTextureId > 0) { + renderer.updateSprite(m_scrollThumbId, sbX, sbY, sbW, sbH, scrollbarThumbTextureId, scrollbarThumbTintColor, thumbLayer); + } else { + renderer.updateRect(m_scrollThumbId, sbX, sbY, sbW, sbH, scrollbarColor, thumbLayer); + } } void UIScrollPanel::updateScrollInteraction(UIContext& ctx) { diff --git a/modules/UIModule/Widgets/UIScrollPanel.h b/modules/UIModule/Widgets/UIScrollPanel.h index 2803621..5e98571 100644 --- a/modules/UIModule/Widgets/UIScrollPanel.h +++ b/modules/UIModule/Widgets/UIScrollPanel.h @@ -48,6 +48,19 @@ public: float borderWidth = 1.0f; uint32_t borderColor = 0x444444FF; + // Texture support + int bgTextureId = 0; // Background texture ID (0 = solid color) + bool useBgTexture = false; // Use texture for background + uint32_t bgTintColor = 0xFFFFFFFF; // Tint for background texture + + int scrollbarTrackTextureId = 0; // Scrollbar track texture ID (0 = solid color) + bool useScrollbarTrackTexture = false; // Use texture for scrollbar track + uint32_t scrollbarTrackTintColor = 0xFFFFFFFF; // Tint for scrollbar track texture + + int scrollbarThumbTextureId = 0; // Scrollbar thumb texture ID (0 = solid color) + bool useScrollbarThumbTexture = false; // Use texture for scrollbar thumb + uint32_t scrollbarThumbTintColor = 0xFFFFFFFF; // Tint for scrollbar thumb texture + // Interaction state bool isDraggingContent = false; bool isDraggingScrollbar = false; diff --git a/modules/UIModule/Widgets/UISlider.cpp b/modules/UIModule/Widgets/UISlider.cpp index 2455af7..fc421c7 100644 --- a/modules/UIModule/Widgets/UISlider.cpp +++ b/modules/UIModule/Widgets/UISlider.cpp @@ -38,16 +38,28 @@ void UISlider::render(UIRenderer& renderer) { // Render track (background) int trackLayer = renderer.nextLayer(); - renderer.updateRect(m_renderId, absX, absY, width, height, trackColor, trackLayer); + if (useTrackTexture && trackTextureId > 0) { + renderer.updateSprite(m_renderId, absX, absY, width, height, trackTextureId, trackTintColor, trackLayer); + } else { + renderer.updateRect(m_renderId, absX, absY, width, height, trackColor, trackLayer); + } // Render fill (progress) int fillLayer = renderer.nextLayer(); if (horizontal) { float fillWidth = (value - minValue) / (maxValue - minValue) * width; - renderer.updateRect(m_fillRenderId, absX, absY, fillWidth, height, fillColor, fillLayer); + if (useFillTexture && fillTextureId > 0) { + renderer.updateSprite(m_fillRenderId, absX, absY, fillWidth, height, fillTextureId, fillTintColor, fillLayer); + } else { + renderer.updateRect(m_fillRenderId, absX, absY, fillWidth, height, fillColor, fillLayer); + } } else { float fillHeight = (value - minValue) / (maxValue - minValue) * height; - renderer.updateRect(m_fillRenderId, absX, absY + height - fillHeight, width, fillHeight, fillColor, fillLayer); + if (useFillTexture && fillTextureId > 0) { + renderer.updateSprite(m_fillRenderId, absX, absY + height - fillHeight, width, fillHeight, fillTextureId, fillTintColor, fillLayer); + } else { + renderer.updateRect(m_fillRenderId, absX, absY + height - fillHeight, width, fillHeight, fillColor, fillLayer); + } } // Render handle @@ -57,15 +69,29 @@ void UISlider::render(UIRenderer& renderer) { // Handle is a small square int handleLayer = renderer.nextLayer(); float halfHandle = handleSize * 0.5f; - renderer.updateRect( - m_handleRenderId, - handleX - halfHandle, - handleY - halfHandle, - handleSize, - handleSize, - handleColor, - handleLayer - ); + + if (useHandleTexture && handleTextureId > 0) { + renderer.updateSprite( + m_handleRenderId, + handleX - halfHandle, + handleY - halfHandle, + handleSize, + handleSize, + handleTextureId, + handleTintColor, + handleLayer + ); + } else { + renderer.updateRect( + m_handleRenderId, + handleX - halfHandle, + handleY - halfHandle, + handleSize, + handleSize, + handleColor, + handleLayer + ); + } // Render children on top renderChildren(renderer); diff --git a/modules/UIModule/Widgets/UISlider.h b/modules/UIModule/Widgets/UISlider.h index f1b442e..ff9bf18 100644 --- a/modules/UIModule/Widgets/UISlider.h +++ b/modules/UIModule/Widgets/UISlider.h @@ -61,6 +61,19 @@ public: uint32_t handleColor = 0xecf0f1FF; float handleSize = 16.0f; + // Texture support + int trackTextureId = 0; // Track texture ID (0 = solid color) + bool useTrackTexture = false; // Use texture for track + uint32_t trackTintColor = 0xFFFFFFFF; // Tint for track texture + + int fillTextureId = 0; // Fill texture ID (0 = solid color) + bool useFillTexture = false; // Use texture for fill + uint32_t fillTintColor = 0xFFFFFFFF; // Tint for fill texture + + int handleTextureId = 0; // Handle texture ID (0 = solid color) + bool useHandleTexture = false; // Use texture for handle + uint32_t handleTintColor = 0xFFFFFFFF; // Tint for handle texture + // State bool isDragging = false; bool isHovered = false;