From af252575cf2f57ce4482840a37c57bca86bf4c8f Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 5 Jun 2026 20:35:18 -0500 Subject: [PATCH] chore: remove dead UI header files (scroll_fade_fbo.h, gpu_mask.h) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both are header-only, in no CMake target, #included nowhere, and their only symbols (ScrollFadeRT, DrawScrollFadeMask) are referenced nowhere: - src/ui/effects/scroll_fade_fbo.h — superseded by the shader-based scroll_fade_shader.h (the implementation actually used by settings_page). - src/ui/material/gpu_mask.h — a GPU blend-mask helper never integrated. App + test build clean after removal; tests pass; hygiene clean. Co-Authored-By: Claude Opus 4.8 --- src/ui/effects/scroll_fade_fbo.h | 418 ------------------------------- src/ui/material/gpu_mask.h | 190 -------------- 2 files changed, 608 deletions(-) delete mode 100644 src/ui/effects/scroll_fade_fbo.h delete mode 100644 src/ui/material/gpu_mask.h diff --git a/src/ui/effects/scroll_fade_fbo.h b/src/ui/effects/scroll_fade_fbo.h deleted file mode 100644 index 5dd523a..0000000 --- a/src/ui/effects/scroll_fade_fbo.h +++ /dev/null @@ -1,418 +0,0 @@ -// DragonX Wallet - ImGui Edition -// Copyright 2024-2026 The Hush Developers -// Released under the GPLv3 -// -// Offscreen render-target scroll fade — the ImGui equivalent of CSS mask-image. -// Renders scrollable content to an offscreen surface, then composites it back -// as a textured mesh strip with vertex alpha for edge fading. -// This produces a true per-pixel fade that works with any background -// (including acrylic/backdrop transparency). -// -// Supports both OpenGL (DRAGONX_HAS_GLAD) and DX11 (DRAGONX_USE_DX11). - -#pragma once - -#include "imgui.h" -#include "imgui_internal.h" -#include - -// ============================================================================ -// Platform detection -// ============================================================================ -#if defined(DRAGONX_USE_DX11) - #include - #define SCROLL_FADE_HAS_OFFSCREEN 1 - #define SCROLL_FADE_DX11 1 -#elif defined(DRAGONX_HAS_GLAD) - #include -#include "../../util/logger.h" - #ifndef GL_FRAMEBUFFER_BINDING - #define GL_FRAMEBUFFER_BINDING 0x8CA6 - #endif - #ifndef GL_VIEWPORT - #define GL_VIEWPORT 0x0BA2 - #endif - #ifndef GL_SCISSOR_TEST - #define GL_SCISSOR_TEST 0x0C11 - #endif - #define SCROLL_FADE_HAS_OFFSCREEN 1 - #define SCROLL_FADE_GL 1 -#endif - -#ifdef SCROLL_FADE_HAS_OFFSCREEN - -namespace dragonx { -namespace ui { -namespace effects { - -// ============================================================================ -// ScrollFadeRT — manages an offscreen render target for scroll-fade rendering -// ============================================================================ - -class ScrollFadeRT { -public: - ScrollFadeRT() = default; - ~ScrollFadeRT() { destroy(); } - - // Non-copyable - ScrollFadeRT(const ScrollFadeRT&) = delete; - ScrollFadeRT& operator=(const ScrollFadeRT&) = delete; - - /// Ensure RT matches the required dimensions. Returns true if ready. - bool ensure(int w, int h) { - if (w <= 0 || h <= 0) return false; - if (isValid() && w == width_ && h == height_) return true; - return init(w, h); - } - - void destroy(); - bool isValid() const; - - /// Get the texture as an ImTextureID for compositing. - ImTextureID textureID() const; - - int width() const { return width_; } - int height() const { return height_; } - -#ifdef SCROLL_FADE_DX11 - ID3D11RenderTargetView* rtv() const { return rtv_; } -#endif -#ifdef SCROLL_FADE_GL - unsigned int fbo() const { return fbo_; } -#endif - -private: - bool init(int w, int h); - - int width_ = 0; - int height_ = 0; - -#ifdef SCROLL_FADE_DX11 - ID3D11Texture2D* tex_ = nullptr; - ID3D11RenderTargetView* rtv_ = nullptr; - ID3D11ShaderResourceView* srv_ = nullptr; -#endif -#ifdef SCROLL_FADE_GL - unsigned int fbo_ = 0; - unsigned int colorTex_ = 0; -#endif -}; - -// ============================================================================ -// Implementations -// ============================================================================ - -#ifdef SCROLL_FADE_DX11 - -// --- DX11 helpers to get device/context from ImGui backend --- -inline ID3D11Device* GetDX11Device() { - ImGuiIO& io = ImGui::GetIO(); - if (!io.BackendRendererUserData) return nullptr; - return *reinterpret_cast(io.BackendRendererUserData); -} -inline ID3D11DeviceContext* GetDX11Context() { - ID3D11Device* dev = GetDX11Device(); - if (!dev) return nullptr; - ID3D11DeviceContext* ctx = nullptr; - dev->GetImmediateContext(&ctx); - return ctx; // caller must Release() -} - -inline bool ScrollFadeRT::init(int w, int h) { - destroy(); - ID3D11Device* dev = GetDX11Device(); - if (!dev) return false; - - width_ = w; - height_ = h; - - // Create texture - D3D11_TEXTURE2D_DESC td = {}; - td.Width = (UINT)w; - td.Height = (UINT)h; - td.MipLevels = 1; - td.ArraySize = 1; - td.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - td.SampleDesc.Count = 1; - td.Usage = D3D11_USAGE_DEFAULT; - td.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; - - if (FAILED(dev->CreateTexture2D(&td, nullptr, &tex_))) { - DEBUG_LOGF("ScrollFadeRT: CreateTexture2D failed\n"); - destroy(); - return false; - } - - // Render target view - if (FAILED(dev->CreateRenderTargetView(tex_, nullptr, &rtv_))) { - DEBUG_LOGF("ScrollFadeRT: CreateRenderTargetView failed\n"); - destroy(); - return false; - } - - // Shader resource view (for sampling as texture) - if (FAILED(dev->CreateShaderResourceView(tex_, nullptr, &srv_))) { - DEBUG_LOGF("ScrollFadeRT: CreateShaderResourceView failed\n"); - destroy(); - return false; - } - - return true; -} - -inline void ScrollFadeRT::destroy() { - if (srv_) { srv_->Release(); srv_ = nullptr; } - if (rtv_) { rtv_->Release(); rtv_ = nullptr; } - if (tex_) { tex_->Release(); tex_ = nullptr; } - width_ = height_ = 0; -} - -inline bool ScrollFadeRT::isValid() const { return rtv_ != nullptr; } - -inline ImTextureID ScrollFadeRT::textureID() const { - return (ImTextureID)srv_; -} - -#endif // SCROLL_FADE_DX11 - -#ifdef SCROLL_FADE_GL - -inline bool ScrollFadeRT::init(int w, int h) { - destroy(); - width_ = w; - height_ = h; - - glGenFramebuffers(1, &fbo_); - glBindFramebuffer(GL_FRAMEBUFFER, fbo_); - - glGenTextures(1, &colorTex_); - glBindTexture(GL_TEXTURE_2D, colorTex_); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, - GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, colorTex_, 0); - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - - if (status != GL_FRAMEBUFFER_COMPLETE) { - DEBUG_LOGF("ScrollFadeRT: FBO incomplete (0x%X)\n", status); - destroy(); - return false; - } - return true; -} - -inline void ScrollFadeRT::destroy() { - if (colorTex_) { glDeleteTextures(1, &colorTex_); colorTex_ = 0; } - if (fbo_) { glDeleteFramebuffers(1, &fbo_); fbo_ = 0; } - width_ = height_ = 0; -} - -inline bool ScrollFadeRT::isValid() const { return fbo_ != 0; } - -inline ImTextureID ScrollFadeRT::textureID() const { - return (ImTextureID)(intptr_t)colorTex_; -} - -#endif // SCROLL_FADE_GL - -// ============================================================================ -// Callback state — singleton storage for bind/unbind data -// ============================================================================ - -struct ScrollFadeState { -#ifdef SCROLL_FADE_DX11 - ID3D11RenderTargetView* offscreenRTV = nullptr; - ID3D11RenderTargetView* savedRTV = nullptr; - ID3D11DepthStencilView* savedDSV = nullptr; - D3D11_VIEWPORT savedVP = {}; -#endif -#ifdef SCROLL_FADE_GL - unsigned int fbo = 0; - int savedFBO = 0; - int savedVP[4] = {}; - bool savedScissorEnabled = true; // ImGui always has scissor enabled -#endif - int vpW = 0, vpH = 0; // framebuffer pixel dimensions for viewport -}; - -inline ScrollFadeState& GetScrollFadeState() { - static ScrollFadeState s; - return s; -} - -// ============================================================================ -// Callbacks — inserted into the draw list via AddCallback -// ============================================================================ - -#ifdef SCROLL_FADE_DX11 - -inline void BindRTCallback(const ImDrawList*, const ImDrawCmd*) { - auto& st = GetScrollFadeState(); - ID3D11DeviceContext* ctx = GetDX11Context(); - if (!ctx) return; - - // Save current RT and viewport - UINT numVP = 1; - ctx->OMGetRenderTargets(1, &st.savedRTV, &st.savedDSV); - ctx->RSGetViewports(&numVP, &st.savedVP); - - // Bind offscreen RT - ctx->OMSetRenderTargets(1, &st.offscreenRTV, nullptr); - - // Set viewport to match RT size - D3D11_VIEWPORT vp = {}; - vp.Width = (FLOAT)st.vpW; - vp.Height = (FLOAT)st.vpH; - vp.MaxDepth = 1.0f; - ctx->RSSetViewports(1, &vp); - - // Clear to transparent - float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - ctx->ClearRenderTargetView(st.offscreenRTV, clearColor); - - ctx->Release(); -} - -inline void UnbindRTCallback(const ImDrawList*, const ImDrawCmd*) { - auto& st = GetScrollFadeState(); - ID3D11DeviceContext* ctx = GetDX11Context(); - if (!ctx) return; - - // Restore previous RT and viewport - ctx->OMSetRenderTargets(1, &st.savedRTV, st.savedDSV); - ctx->RSSetViewports(1, &st.savedVP); - - // Release the refs from OMGetRenderTargets - if (st.savedRTV) { st.savedRTV->Release(); st.savedRTV = nullptr; } - if (st.savedDSV) { st.savedDSV->Release(); st.savedDSV = nullptr; } - - ctx->Release(); -} - -#endif // SCROLL_FADE_DX11 - -#ifdef SCROLL_FADE_GL - -inline void BindRTCallback(const ImDrawList*, const ImDrawCmd*) { - auto& st = GetScrollFadeState(); - - // Save current FBO and viewport - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &st.savedFBO); - glGetIntegerv(GL_VIEWPORT, st.savedVP); - - glBindFramebuffer(GL_FRAMEBUFFER, st.fbo); - glViewport(0, 0, st.vpW, st.vpH); - - // Disable scissor test inside the FBO. ImGui's renderer computes - // scissor rects relative to the main framebuffer dimensions — those - // coordinates would be wrong for our offscreen surface. The child - // window's content is already bounded by ImGui's layout, and the - // composite step applies its own clip rect, so skipping scissor - // in the FBO is safe. - glDisable(GL_SCISSOR_TEST); - - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); -} - -inline void UnbindRTCallback(const ImDrawList*, const ImDrawCmd*) { - auto& st = GetScrollFadeState(); - glBindFramebuffer(GL_FRAMEBUFFER, (unsigned int)st.savedFBO); - glViewport(st.savedVP[0], st.savedVP[1], st.savedVP[2], st.savedVP[3]); - if (st.savedScissorEnabled) - glEnable(GL_SCISSOR_TEST); -} - -#endif // SCROLL_FADE_GL - -// ============================================================================ -// Composite helper — draw the RT texture as a mesh strip with alpha fade -// ============================================================================ - -/// Draw the offscreen texture onto `dl` as a vertical strip with alpha=0 at -/// the faded edges and alpha=1 in the middle. Produces a true CSS-like -/// mask-image: linear-gradient() result. -/// -/// @param logicalW, logicalH Logical display dimensions (DisplaySize) for -/// UV calculation — NOT the RT pixel dimensions. ImGui screen coords -/// are in logical units, and the FBO projection maps them 1:1 to the -/// logical coordinate space, so UVs must divide by logical size. -inline void CompositeWithFade(ImDrawList* dl, - ImTextureID texID, - const ImVec2& screenMin, - const ImVec2& screenMax, - int logicalW, int logicalH, - float fadeTop, float fadeBot, - bool needTop, bool needBot) -{ - float left = screenMin.x; - float right = screenMax.x; - float y0 = screenMin.y; - float y1 = screenMin.y + (needTop ? fadeTop : 0.0f); - float y2 = screenMax.y - (needBot ? fadeBot : 0.0f); - float y3 = screenMax.y; - - // Clamp in case fade zones overlap - if (y1 > y2) { float mid = (y0 + y3) * 0.5f; y1 = y2 = mid; } - - // UV coordinates — map screen position (logical) to render target texture. - // Screen coords are in logical (DisplaySize) space. The FBO projection - // maps these 1:1, so divide by logical dimensions to get [0,1] UVs. - float uL = screenMin.x / (float)logicalW; - float uR = screenMax.x / (float)logicalW; - -#ifdef SCROLL_FADE_GL - // OpenGL: FBO Y is flipped (ImGui top=0 → GL bottom=0) - auto uvY = [&](float y) -> float { return 1.0f - y / (float)logicalH; }; -#else - // DX11: no Y flip (both ImGui and DX11 have (0,0) at top-left) - auto uvY = [&](float y) -> float { return y / (float)logicalH; }; -#endif - - ImU32 colOpaque = IM_COL32(255, 255, 255, 255); - ImU32 colClear = IM_COL32(255, 255, 255, 0); - ImU32 colTop = needTop ? colClear : colOpaque; - ImU32 colBot = needBot ? colClear : colOpaque; - - dl->PushTextureID(texID); - dl->PrimReserve(18, 8); - - ImDrawVert* vtx = dl->_VtxWritePtr; - ImDrawIdx* idx = dl->_IdxWritePtr; - ImDrawIdx base = (ImDrawIdx)dl->_VtxCurrentIdx; - - vtx[0] = { ImVec2(left, y0), ImVec2(uL, uvY(y0)), colTop }; - vtx[1] = { ImVec2(right, y0), ImVec2(uR, uvY(y0)), colTop }; - vtx[2] = { ImVec2(left, y1), ImVec2(uL, uvY(y1)), colOpaque }; - vtx[3] = { ImVec2(right, y1), ImVec2(uR, uvY(y1)), colOpaque }; - vtx[4] = { ImVec2(left, y2), ImVec2(uL, uvY(y2)), colOpaque }; - vtx[5] = { ImVec2(right, y2), ImVec2(uR, uvY(y2)), colOpaque }; - vtx[6] = { ImVec2(left, y3), ImVec2(uL, uvY(y3)), colBot }; - vtx[7] = { ImVec2(right, y3), ImVec2(uR, uvY(y3)), colBot }; - - idx[0] = base+0; idx[1] = base+1; idx[2] = base+3; - idx[3] = base+0; idx[4] = base+3; idx[5] = base+2; - idx[6] = base+2; idx[7] = base+3; idx[8] = base+5; - idx[9] = base+2; idx[10] = base+5; idx[11] = base+4; - idx[12] = base+4; idx[13] = base+5; idx[14] = base+7; - idx[15] = base+4; idx[16] = base+7; idx[17] = base+6; - - dl->_VtxWritePtr += 8; - dl->_IdxWritePtr += 18; - dl->_VtxCurrentIdx += 8; - - dl->PopTextureID(); -} - -} // namespace effects -} // namespace ui -} // namespace dragonx - -#endif // SCROLL_FADE_HAS_OFFSCREEN diff --git a/src/ui/material/gpu_mask.h b/src/ui/material/gpu_mask.h deleted file mode 100644 index 0def428..0000000 --- a/src/ui/material/gpu_mask.h +++ /dev/null @@ -1,190 +0,0 @@ -// DragonX Wallet - ImGui Edition -// Copyright 2024-2026 The Hush Developers -// Released under the GPLv3 -// -// GPU alpha mask — the ImGui equivalent of CSS mask-image: linear-gradient(). -// Uses AddCallback to switch the GPU blend mode so that gradient quads -// multiply the framebuffer's alpha (and RGB) by the source alpha, producing -// a smooth per-pixel fade without vertex-spacing artefacts. - -#pragma once - -#include "imgui.h" - -#ifdef DRAGONX_USE_DX11 - #include -#else - #ifdef DRAGONX_HAS_GLAD - #include - #else - #include - #endif - #include // for SDL_GL_GetProcAddress -#endif - -namespace dragonx { -namespace ui { -namespace material { - -// ============================================================================ -// Blend-mode callbacks — called by ImGui's backend during draw list rendering -// ============================================================================ - -#ifdef DRAGONX_USE_DX11 - -// Cached DX11 blend state for the mask pass -inline ID3D11BlendState* GetMaskBlendState() { - static ID3D11BlendState* s_maskBlend = nullptr; - if (!s_maskBlend) { - ImGuiIO& io = ImGui::GetIO(); - if (!io.BackendRendererUserData) return nullptr; - ID3D11Device* dev = *reinterpret_cast(io.BackendRendererUserData); - if (!dev) return nullptr; - - D3D11_BLEND_DESC desc = {}; - desc.RenderTarget[0].BlendEnable = TRUE; - desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ZERO; - desc.RenderTarget[0].DestBlend = D3D11_BLEND_SRC_ALPHA; - desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; - desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO; - desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_SRC_ALPHA; - desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; - desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - dev->CreateBlendState(&desc, &s_maskBlend); - } - return s_maskBlend; -} - -// Switch to mask blend: dst *= srcAlpha (both RGB and A) -inline void MaskBlendCallback(const ImDrawList*, const ImDrawCmd*) { - ImGuiIO& io = ImGui::GetIO(); - if (!io.BackendRendererUserData) return; - // The ImGui DX11 backend stores the device as the first pointer - ID3D11Device* dev = *reinterpret_cast(io.BackendRendererUserData); - if (!dev) return; - ID3D11DeviceContext* ctx = nullptr; - dev->GetImmediateContext(&ctx); - if (!ctx) return; - - ID3D11BlendState* bs = GetMaskBlendState(); - if (bs) { - float blendFactor[4] = {0, 0, 0, 0}; - ctx->OMSetBlendState(bs, blendFactor, 0xFFFFFFFF); - } - ctx->Release(); -} - -// Restore normal ImGui blend: src*srcA + dst*(1-srcA) -inline void RestoreBlendCallback(const ImDrawList*, const ImDrawCmd*) { - ImGuiIO& io = ImGui::GetIO(); - if (!io.BackendRendererUserData) return; - ID3D11Device* dev = *reinterpret_cast(io.BackendRendererUserData); - if (!dev) return; - ID3D11DeviceContext* ctx = nullptr; - dev->GetImmediateContext(&ctx); - if (!ctx) return; - // Setting nullptr restores the default blend state that ImGui's DX11 - // backend configures at the start of each frame. - ctx->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); - ctx->Release(); -} - -#else // OpenGL - -// glBlendFuncSeparate may not be in the GLAD profile — load it once via SDL. -typedef void (*PFN_glBlendFuncSeparate)(GLenum, GLenum, GLenum, GLenum); -inline PFN_glBlendFuncSeparate GetBlendFuncSeparate() { - static PFN_glBlendFuncSeparate fn = nullptr; - static bool resolved = false; - if (!resolved) { - resolved = true; - fn = (PFN_glBlendFuncSeparate)(void*)SDL_GL_GetProcAddress("glBlendFuncSeparate"); - } - return fn; -} - -inline void MaskBlendCallback(const ImDrawList*, const ImDrawCmd*) { - // dst.rgb = dst.rgb * srcAlpha (erase content where mask alpha < 1) - // dst.a = dst.a * srcAlpha (match alpha channel) - auto fn = GetBlendFuncSeparate(); - if (fn) - fn(GL_ZERO, GL_SRC_ALPHA, GL_ZERO, GL_SRC_ALPHA); - else - glBlendFunc(GL_ZERO, GL_SRC_ALPHA); -} - -inline void RestoreBlendCallback(const ImDrawList*, const ImDrawCmd*) { - // Restore ImGui's exact blend state: - // RGB: src*srcA + dst*(1-srcA) - // Alpha: src*1 + dst*(1-srcA) - auto fn = GetBlendFuncSeparate(); - if (fn) - fn(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - else - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -} - -#endif - -// ============================================================================ -// DrawScrollFadeMask — draw gradient quads that mask the top/bottom edges -// of a scrollable child region, producing a smooth per-pixel fade. -// -// Call this on the child's draw list BEFORE EndChild(). -// The gradient quads use the mask blend mode to multiply the existing -// framebuffer content by their alpha, so alpha=1 means "keep" and -// alpha=0 means "erase to transparent/black". -// -// Parameters: -// dl — the child window's draw list (ImGui::GetWindowDrawList()) -// clipMin/Max — the child window's visible area (screen coords) -// fadeH — the height of the fade zone in pixels -// scrollY — current scroll offset (ImGui::GetScrollY()) -// scrollMaxY — maximum scroll offset (ImGui::GetScrollMaxY()) -// ============================================================================ -inline void DrawScrollFadeMask(ImDrawList* dl, - const ImVec2& clipMin, const ImVec2& clipMax, - float fadeH, - float scrollY, float scrollMaxY) -{ - if (fadeH <= 0.0f) return; - - bool needTop = scrollY > 1.0f; - bool needBottom = scrollMaxY > 0 && scrollY < scrollMaxY - 1.0f; - if (!needTop && !needBottom) return; - - float left = clipMin.x; - float right = clipMax.x; - - // Switch to mask blend mode - dl->AddCallback(MaskBlendCallback, nullptr); - - if (needTop) { - // Top gradient: alpha=0 at top edge (erase) → alpha=1 at top+fadeH (keep) - ImVec2 tMin(left, clipMin.y); - ImVec2 tMax(right, clipMin.y + fadeH); - ImU32 transparent = IM_COL32(0, 0, 0, 0); - ImU32 opaque = IM_COL32(0, 0, 0, 255); - dl->AddRectFilledMultiColor(tMin, tMax, - transparent, transparent, // top-left, top-right - opaque, opaque); // bottom-left, bottom-right - } - - if (needBottom) { - // Bottom gradient: alpha=1 at bottom-fadeH (keep) → alpha=0 at bottom (erase) - ImVec2 bMin(left, clipMax.y - fadeH); - ImVec2 bMax(right, clipMax.y); - ImU32 opaque = IM_COL32(0, 0, 0, 255); - ImU32 transparent = IM_COL32(0, 0, 0, 0); - dl->AddRectFilledMultiColor(bMin, bMax, - opaque, opaque, // top-left, top-right - transparent, transparent); // bottom-left, bottom-right - } - - // Restore normal blend mode - dl->AddCallback(RestoreBlendCallback, nullptr); -} - -} // namespace material -} // namespace ui -} // namespace dragonx