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)
419 lines
14 KiB
C++
419 lines
14 KiB
C++
// 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
|