ObsidianDragon - DragonX ImGui Wallet
Full-node GUI wallet for DragonX cryptocurrency. Built with Dear ImGui, SDL3, and OpenGL3/DX11. Features: - Send/receive shielded and transparent transactions - Autoshield with merged transaction display - Built-in CPU mining (xmrig) - Peer management and network monitoring - Wallet encryption with PIN lock - QR code generation for receive addresses - Transaction history with pagination - Console for direct RPC commands - Cross-platform (Linux, Windows)
This commit is contained in:
418
src/ui/effects/scroll_fade_fbo.h
Normal file
418
src/ui/effects/scroll_fade_fbo.h
Normal file
@@ -0,0 +1,418 @@
|
||||
// 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 <cstdio>
|
||||
|
||||
// ============================================================================
|
||||
// Platform detection
|
||||
// ============================================================================
|
||||
#if defined(DRAGONX_USE_DX11)
|
||||
#include <d3d11.h>
|
||||
#define SCROLL_FADE_HAS_OFFSCREEN 1
|
||||
#define SCROLL_FADE_DX11 1
|
||||
#elif defined(DRAGONX_HAS_GLAD)
|
||||
#include <glad/gl.h>
|
||||
#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<ID3D11Device**>(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
|
||||
Reference in New Issue
Block a user