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:
2026-02-26 02:31:52 -06:00
commit 3aee55b49c
306 changed files with 177789 additions and 0 deletions

1695
src/ui/effects/acrylic.cpp Normal file

File diff suppressed because it is too large Load Diff

456
src/ui/effects/acrylic.h Normal file
View File

@@ -0,0 +1,456 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "framebuffer.h"
#include "blur_shader.h"
#include "noise_texture.h"
#include "imgui.h"
#include <memory>
#include <string>
#ifdef DRAGONX_USE_DX11
#include <d3d11.h>
#endif
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief Acrylic material parameters
*
* These parameters control the appearance of the acrylic effect,
* matching Microsoft's Fluent Design system.
*/
struct AcrylicParams {
// Tint color (RGBA, pre-multiplied alpha)
ImVec4 tintColor = ImVec4(0.1f, 0.1f, 0.12f, 1.0f);
// Tint opacity (0.0 = fully transparent, 1.0 = fully opaque)
float tintOpacity = 0.75f;
// Luminosity opacity (controls saturation pass-through)
// Lower values = more desaturated/milky appearance
float luminosityOpacity = 0.5f;
// Blur radius in pixels (typical: 20-60)
float blurRadius = 30.0f;
// Noise texture opacity (typical: 0.02-0.04)
float noiseOpacity = 0.02f;
// Fallback color when acrylic is disabled.
// The alpha channel also controls the glass opacity of the blurred
// background when acrylic IS active (lower alpha = more see-through).
ImVec4 fallbackColor = ImVec4(0.15f, 0.15f, 0.18f, 0.5f);
// Whether acrylic is enabled (false = use fallback)
bool enabled = true;
};
/**
* @brief Acrylic quality levels
*/
enum class AcrylicQuality {
Off, // Solid fallback color only
Low, // Single blur pass, downsampled
Medium, // Two blur passes, slight downsample
High // Full quality blur
};
/**
* @brief Acrylic fallback modes for graceful degradation
*/
enum class AcrylicFallback {
None, // Full acrylic (blur + tint + noise)
TintedOnly, // No blur, just semi-transparent tint overlay
Solid // Opaque fallback color only
};
/**
* @brief Acrylic settings for accessibility and performance
*/
struct AcrylicSettings {
bool enabled = true; // Master toggle
bool reducedTransparency = false; // Accessibility: use solid colors
float uiOpacity = 1.0f; // Card/sidebar opacity multiplier (0.31.0)
AcrylicQuality quality = AcrylicQuality::Medium; // Medium default for balance of quality/performance
float blurRadiusMultiplier = 1.0f; // Scales all blur radii (0.5 - 2.0)
float noiseOpacityMultiplier = 1.0f; // Scales noise opacity (0.0 - 2.0)
// Auto-disable conditions (checked each frame)
bool disableOnBattery = false; // Disable when on battery power
bool disableOnLowFPS = false; // Disable if FPS drops below threshold
float lowFPSThreshold = 30.0f; // FPS threshold for auto-disable
};
/**
* @brief System capabilities for acrylic effects
*/
struct AcrylicCapabilities {
bool hasFramebufferSupport = false;
bool hasShaderSupport = false;
bool hasTextureSupport = false;
int maxTextureSize = 0;
std::string glVersion;
std::string glRenderer;
bool isLowEndGPU = false;
bool isOnBattery = false; // Linux: check /sys/class/power_supply
};
/**
* @brief Main acrylic material rendering system
*
* Implements Microsoft Fluent Design's acrylic material effect:
* - Gaussian blur of background content
* - Tint color overlay
* - Luminosity blend for depth
* - Subtle noise texture for grain
*/
class AcrylicMaterial {
public:
AcrylicMaterial();
~AcrylicMaterial();
// Non-copyable
AcrylicMaterial(const AcrylicMaterial&) = delete;
AcrylicMaterial& operator=(const AcrylicMaterial&) = delete;
/**
* @brief Initialize the acrylic system
* @return true if successful
*/
bool init();
/**
* @brief Release all resources
*/
void shutdown();
/**
* @brief Check if system is initialized
*/
bool isInitialized() const { return initialized_; }
/**
* @brief Update internal buffers for new viewport size
* @param width Viewport width
* @param height Viewport height
*/
void resize(int width, int height);
/**
* @brief Capture current screen content for blur source
*
* Call this BEFORE rendering any acrylic surfaces.
* Only recaptures when the background is marked dirty.
*/
void captureBackground();
/**
* @brief Capture background directly from the current framebuffer.
*
* Intended to be called from an ImGui draw callback inserted at
* the end of the BackgroundDrawList. At that point during
* RenderDrawData(), only the background (gradient / image / noise)
* has been rasterized — no UI elements are in the framebuffer yet.
* This gives a clean background capture for acrylic blur.
*
* Respects the dirtyFrames_ counter so the capture and blur
* are only recomputed when the background actually changes
* (resize, theme change, etc.).
*/
void captureBackgroundDirect();
/**
* @brief Returns true once a valid background capture has been made.
*
* drawRect() checks this and falls back to a tinted fill on the
* very first frame before the callback has fired.
*/
bool hasValidCapture() const { return hasValidCapture_; }
/// Mark background as needing recapture (call on resize, theme change, etc.)
/// Uses a 2-frame counter so the capture re-runs after the new background
/// has actually been rendered (theme switch happens mid-frame, before the
/// BackgroundDrawList is rebuilt with new colors/images).
void markBackgroundDirty() { dirtyFrames_ = 2; }
/**
* @brief Render an acrylic-filled rectangle
*
* @param drawList ImGui draw list to add commands to
* @param pMin Top-left corner in screen coordinates
* @param pMax Bottom-right corner in screen coordinates
* @param params Acrylic parameters
* @param rounding Corner rounding
*/
void drawRect(ImDrawList* drawList, const ImVec2& pMin, const ImVec2& pMax,
const AcrylicParams& params, float rounding = 0.0f);
/**
* @brief Get the blurred background texture for manual rendering
*/
ImTextureID getBlurredTexture() const;
/**
* @brief Get the noise texture
*/
ImTextureID getNoiseTexture() const;
// ========================================================================
// Settings
// ========================================================================
/**
* @brief Set global acrylic quality
*/
void setQuality(AcrylicQuality quality);
AcrylicQuality getQuality() const { return settings_.quality; }
/**
* @brief Enable/disable acrylic globally
*/
void setEnabled(bool enabled) { settings_.enabled = enabled; }
bool isEnabled() const { return settings_.enabled; }
/**
* @brief Set blur radius multiplier (scales all blur radii)
* @param multiplier Value from 0.0 to 5.0
*/
void setBlurMultiplier(float multiplier) {
float clamped = std::max(0.0f, std::min(5.0f, multiplier));
settings_.blurRadiusMultiplier = clamped;
}
float getBlurMultiplier() const { return settings_.blurRadiusMultiplier; }
/**
* @brief Set reduced transparency mode (accessibility)
*/
void setReducedTransparency(bool reduced) { settings_.reducedTransparency = reduced; }
bool getReducedTransparency() const { return settings_.reducedTransparency; }
/**
* @brief Set UI opacity multiplier for cards/sidebar (0.31.0, 1=opaque)
*/
void setUIOpacity(float opacity) {
settings_.uiOpacity = std::max(0.3f, std::min(1.0f, opacity));
}
float getUIOpacity() const { return settings_.uiOpacity; }
/**
* @brief Set noise opacity multiplier (0.0 = no noise, 1.0 = default, 2.0 = double)
*/
void setNoiseOpacityMultiplier(float m) {
settings_.noiseOpacityMultiplier = std::max(0.0f, std::min(5.0f, m));
}
float getNoiseOpacityMultiplier() const { return settings_.noiseOpacityMultiplier; }
/**
* @brief Get full settings struct
*/
const AcrylicSettings& getSettings() const { return settings_; }
void setSettings(const AcrylicSettings& settings) { settings_ = settings; }
// ========================================================================
// Fallback System
// ========================================================================
/**
* @brief Detect system capabilities and determine fallback mode
*
* Checks OpenGL capabilities, GPU info, and user preferences to
* determine the appropriate rendering mode.
*
* @return The recommended fallback mode
*/
AcrylicFallback detectFallback() const;
/**
* @brief Get current fallback mode (cached)
*/
AcrylicFallback getCurrentFallback() const { return currentFallback_; }
/**
* @brief Force a specific fallback mode
*/
void setForcedFallback(AcrylicFallback fallback) {
forcedFallback_ = fallback;
hasForcedFallback_ = true;
}
/**
* @brief Clear forced fallback and use auto-detection
*/
void clearForcedFallback() { hasForcedFallback_ = false; }
/**
* @brief Get detected system capabilities
*/
const AcrylicCapabilities& getCapabilities() const { return capabilities_; }
/**
* @brief Refresh capability detection (call after context changes)
*/
void refreshCapabilities();
// ========================================================================
// Preset Parameters
// ========================================================================
/**
* @brief Get dark theme acrylic preset
*/
static AcrylicParams getDarkPreset();
/**
* @brief Get light theme acrylic preset
*/
static AcrylicParams getLightPreset();
/**
* @brief Get DragonX branded acrylic preset
*/
static AcrylicParams getDragonXPreset();
/**
* @brief Get popup/dialog acrylic preset
*/
static AcrylicParams getPopupPreset();
/**
* @brief Get sidebar acrylic preset
*/
static AcrylicParams getSidebarPreset();
private:
/**
* @brief Apply blur passes to captured content
*/
void applyBlur(float radius);
/**
* @brief Composite final acrylic appearance
*/
void compositeAcrylic(const ImVec2& pMin, const ImVec2& pMax,
const AcrylicParams& params);
/**
* @brief Check if effect should be skipped (settings/quality)
*/
bool shouldSkipEffect() const;
/**
* @brief Draw tinted-only fallback (no blur)
*/
void drawTintedRect(ImDrawList* drawList, const ImVec2& pMin, const ImVec2& pMax,
const AcrylicParams& params, float rounding);
bool initialized_ = false;
AcrylicSettings settings_; // All settings in one struct
// Fallback system
AcrylicCapabilities capabilities_;
AcrylicFallback currentFallback_ = AcrylicFallback::None;
AcrylicFallback forcedFallback_ = AcrylicFallback::None;
bool hasForcedFallback_ = false;
// Screen dimensions
int viewportWidth_ = 0;
int viewportHeight_ = 0;
// Noise texture config
int noiseTextureSize_ = 256; // Texture resolution (larger = less repetition)
// Caching for performance
float lastBlurRadius_ = 0.0f;
bool blurCacheValid_ = false;
int dirtyFrames_ = 2; // >0 means recapture needed; counts down each frame
bool hasValidCapture_ = false; // true once a clean BG capture exists
// Capture and blur framebuffers
Framebuffer captureBuffer_;
FramebufferPingPong blurBuffers_;
// Shaders
BlurShader blurShader_;
// Fullscreen quad for post-processing
FullscreenQuad quad_;
// Noise texture
NoiseTexture noiseTexture_;
// Composite shader (tint + noise overlay)
GLuint compositeShader_ = 0;
GLint uCompositeTintColor_ = -1;
GLint uCompositeTintOpacity_ = -1;
GLint uCompositeLuminosity_ = -1;
GLint uCompositeNoiseOpacity_ = -1;
GLint uCompositeBlurTex_ = -1;
GLint uCompositeNoiseTex_ = -1;
GLint uCompositeTexScale_ = -1;
#ifdef DRAGONX_USE_DX11
// ---- DX11 acrylic resources (used instead of GL objects above) ----
ID3D11Device* dx_device_ = nullptr;
ID3D11DeviceContext* dx_context_ = nullptr;
// Capture buffer (full viewport resolution)
ID3D11Texture2D* dx_captureTex_ = nullptr;
ID3D11ShaderResourceView* dx_captureSRV_ = nullptr;
ID3D11RenderTargetView* dx_captureRTV_ = nullptr;
// Blur ping-pong buffers (possibly downscaled)
ID3D11Texture2D* dx_blurTex_[2] = {};
ID3D11ShaderResourceView* dx_blurSRV_[2] = {};
ID3D11RenderTargetView* dx_blurRTV_[2] = {};
int dx_blurWidth_ = 0;
int dx_blurHeight_ = 0;
int dx_blurCurrent_ = 0; // which buffer is "source"
// Shaders & pipeline objects
ID3D11VertexShader* dx_blurVS_ = nullptr;
ID3D11PixelShader* dx_blurPS_ = nullptr;
ID3D11InputLayout* dx_inputLayout_ = nullptr;
ID3D11Buffer* dx_blurCB_ = nullptr;
ID3D11Buffer* dx_vertexBuf_ = nullptr;
ID3D11SamplerState* dx_sampler_ = nullptr;
ID3D11RasterizerState* dx_blurRS_ = nullptr;
ID3D11BlendState* dx_blurBS_ = nullptr;
ID3D11DepthStencilState* dx_blurDSS_ = nullptr;
// Noise texture (DX11)
ID3D11Texture2D* dx_noiseTex_ = nullptr;
ID3D11ShaderResourceView* dx_noiseSRV_ = nullptr;
// Internal helpers
bool dx_initPipeline();
void dx_releasePipeline();
void dx_createRenderTarget(ID3D11Texture2D*& tex,
ID3D11ShaderResourceView*& srv,
ID3D11RenderTargetView*& rtv,
int w, int h,
DXGI_FORMAT format);
void dx_releaseRenderTarget(ID3D11Texture2D*& tex,
ID3D11ShaderResourceView*& srv,
ID3D11RenderTargetView*& rtv);
#endif // DRAGONX_USE_DX11
};
// ============================================================================
// Global Acrylic Instance
// ============================================================================
/**
* @brief Get the global acrylic material instance
*/
AcrylicMaterial& getAcrylicMaterial();
} // namespace effects
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,282 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "blur_shader.h"
#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#include <cstdio>
#include <vector>
#include "../../util/logger.h"
namespace dragonx {
namespace ui {
namespace effects {
// ============================================================================
// BlurShader Implementation (GLEW Available)
// ============================================================================
BlurShader::BlurShader() = default;
BlurShader::~BlurShader()
{
destroy();
}
bool BlurShader::init()
{
// Create shaders
vertexShader_ = glCreateShader(GL_VERTEX_SHADER);
fragmentShader_ = glCreateShader(GL_FRAGMENT_SHADER);
if (!compileShader(vertexShader_, ShaderSource::BLUR_VERTEX)) {
DEBUG_LOGF("BlurShader: Failed to compile vertex shader\n");
destroy();
return false;
}
if (!compileShader(fragmentShader_, ShaderSource::BLUR_FRAGMENT)) {
DEBUG_LOGF("BlurShader: Failed to compile fragment shader\n");
destroy();
return false;
}
// Create program
program_ = glCreateProgram();
glAttachShader(program_, vertexShader_);
glAttachShader(program_, fragmentShader_);
if (!linkProgram()) {
DEBUG_LOGF("BlurShader: Failed to link program\n");
destroy();
return false;
}
// Get uniform locations
uTexture_ = glGetUniformLocation(program_, "uTexture");
uDirection_ = glGetUniformLocation(program_, "uDirection");
uResolution_ = glGetUniformLocation(program_, "uResolution");
uRadius_ = glGetUniformLocation(program_, "uRadius");
DEBUG_LOGF("BlurShader: Initialized successfully\n");
DEBUG_LOGF(" Uniforms: uTexture=%d, uDirection=%d, uResolution=%d, uRadius=%d\n",
uTexture_, uDirection_, uResolution_, uRadius_);
return true;
}
void BlurShader::destroy()
{
if (program_) {
glDeleteProgram(program_);
program_ = 0;
}
if (vertexShader_) {
glDeleteShader(vertexShader_);
vertexShader_ = 0;
}
if (fragmentShader_) {
glDeleteShader(fragmentShader_);
fragmentShader_ = 0;
}
uTexture_ = -1;
uDirection_ = -1;
uResolution_ = -1;
uRadius_ = -1;
}
bool BlurShader::compileShader(unsigned int shader, const char* source)
{
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
GLint logLength;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
std::vector<char> log(logLength + 1);
glGetShaderInfoLog(shader, logLength, nullptr, log.data());
DEBUG_LOGF("Shader compile error:\n%s\n", log.data());
return false;
}
return true;
}
bool BlurShader::linkProgram()
{
glLinkProgram(program_);
GLint success;
glGetProgramiv(program_, GL_LINK_STATUS, &success);
if (!success) {
GLint logLength;
glGetProgramiv(program_, GL_INFO_LOG_LENGTH, &logLength);
std::vector<char> log(logLength + 1);
glGetProgramInfoLog(program_, logLength, nullptr, log.data());
DEBUG_LOGF("Program link error:\n%s\n", log.data());
return false;
}
return true;
}
void BlurShader::bind()
{
glUseProgram(program_);
}
void BlurShader::unbind()
{
glUseProgram(0);
}
void BlurShader::setDirection(bool horizontal)
{
if (uDirection_ >= 0) {
if (horizontal) {
glUniform2f(uDirection_, 1.0f, 0.0f);
} else {
glUniform2f(uDirection_, 0.0f, 1.0f);
}
}
}
void BlurShader::setRadius(float radius)
{
if (uRadius_ >= 0) {
glUniform1f(uRadius_, radius);
}
}
void BlurShader::setResolution(int width, int height)
{
if (uResolution_ >= 0) {
glUniform2f(uResolution_, static_cast<float>(width), static_cast<float>(height));
}
}
void BlurShader::setTexture(int unit)
{
if (uTexture_ >= 0) {
glUniform1i(uTexture_, unit);
}
}
// ============================================================================
// FullscreenQuad Implementation
// ============================================================================
FullscreenQuad::FullscreenQuad() = default;
FullscreenQuad::~FullscreenQuad()
{
destroy();
}
bool FullscreenQuad::init()
{
// Fullscreen quad vertices: position (x,y) and texcoord (u,v)
// NDC coordinates: -1 to 1
static const float vertices[] = {
// Position // TexCoord
-1.0f, 1.0f, 0.0f, 1.0f, // Top-left
-1.0f, -1.0f, 0.0f, 0.0f, // Bottom-left
1.0f, -1.0f, 1.0f, 0.0f, // Bottom-right
-1.0f, 1.0f, 0.0f, 1.0f, // Top-left
1.0f, -1.0f, 1.0f, 0.0f, // Bottom-right
1.0f, 1.0f, 1.0f, 1.0f // Top-right
};
glGenVertexArrays(1, &vao_);
glGenBuffers(1, &vbo_);
glBindVertexArray(vao_);
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Position attribute (location = 0)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// TexCoord attribute (location = 1)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
return vao_ != 0;
}
void FullscreenQuad::destroy()
{
if (vbo_) {
glDeleteBuffers(1, &vbo_);
vbo_ = 0;
}
if (vao_) {
glDeleteVertexArrays(1, &vao_);
vao_ = 0;
}
}
void FullscreenQuad::draw()
{
glBindVertexArray(vao_);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
} // namespace effects
} // namespace ui
} // namespace dragonx
#else // !DRAGONX_HAS_GLAD
// ============================================================================
// Stub Implementation (No GLAD - Acrylic effects disabled)
// ============================================================================
namespace dragonx {
namespace ui {
namespace effects {
BlurShader::BlurShader() = default;
BlurShader::~BlurShader() = default;
bool BlurShader::init() { return false; }
void BlurShader::destroy() {}
bool BlurShader::compileShader(unsigned int, const char*) { return false; }
bool BlurShader::linkProgram() { return false; }
void BlurShader::bind() {}
void BlurShader::unbind() {}
void BlurShader::setDirection(bool) {}
void BlurShader::setRadius(float) {}
void BlurShader::setResolution(int, int) {}
void BlurShader::setTexture(int) {}
FullscreenQuad::FullscreenQuad() = default;
FullscreenQuad::~FullscreenQuad() = default;
bool FullscreenQuad::init() { return false; }
void FullscreenQuad::destroy() {}
void FullscreenQuad::draw() {}
} // namespace effects
} // namespace ui
} // namespace dragonx
#endif // DRAGONX_HAS_GLAD

View File

@@ -0,0 +1,211 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#ifndef GLAD_GL_H_
typedef unsigned int GLuint;
typedef int GLint;
typedef unsigned int GLenum;
typedef int GLsizei;
#endif
#include <string>
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief GLSL shader program for blur effects
*
* Implements a two-pass Gaussian blur using separable convolution.
* First pass blurs horizontally, second pass blurs vertically.
*/
class BlurShader {
public:
BlurShader();
~BlurShader();
// Non-copyable
BlurShader(const BlurShader&) = delete;
BlurShader& operator=(const BlurShader&) = delete;
/**
* @brief Compile and link the blur shader program
* @return true if successful
*/
bool init();
/**
* @brief Release shader resources
*/
void destroy();
/**
* @brief Use this shader program
*/
void bind();
/**
* @brief Stop using this shader
*/
void unbind();
/**
* @brief Set the blur direction
* @param horizontal true for horizontal pass, false for vertical
*/
void setDirection(bool horizontal);
/**
* @brief Set the blur radius/intensity
* @param radius Blur radius in pixels (typical: 1.0 - 10.0)
*/
void setRadius(float radius);
/**
* @brief Set the texture resolution for proper texel calculation
* @param width Texture width
* @param height Texture height
*/
void setResolution(int width, int height);
/**
* @brief Set the input texture unit
* @param unit Texture unit (0 = GL_TEXTURE0)
*/
void setTexture(int unit = 0);
/**
* @brief Check if shader is valid
*/
bool isValid() const { return program_ != 0; }
/**
* @brief Get the shader program ID
*/
GLuint getProgram() const { return program_; }
private:
bool compileShader(GLuint shader, const char* source);
bool linkProgram();
GLuint program_ = 0;
GLuint vertexShader_ = 0;
GLuint fragmentShader_ = 0;
// Uniform locations
GLint uTexture_ = -1;
GLint uDirection_ = -1;
GLint uResolution_ = -1;
GLint uRadius_ = -1;
};
/**
* @brief Manages the full-screen quad VAO for post-processing
*/
class FullscreenQuad {
public:
FullscreenQuad();
~FullscreenQuad();
/**
* @brief Initialize the quad VAO/VBO
*/
bool init();
/**
* @brief Release resources
*/
void destroy();
/**
* @brief Draw the fullscreen quad
*/
void draw();
bool isValid() const { return vao_ != 0; }
private:
GLuint vao_ = 0;
GLuint vbo_ = 0;
};
// ============================================================================
// Shader Source Code (embedded)
// ============================================================================
namespace ShaderSource {
// Simple passthrough vertex shader for fullscreen quad
constexpr const char* BLUR_VERTEX = R"glsl(
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 vTexCoord;
void main()
{
gl_Position = vec4(aPos, 0.0, 1.0);
vTexCoord = aTexCoord;
}
)glsl";
// Gaussian blur fragment shader (dynamic kernel, separable)
// Loop-based with 1-texel spacing + triangular dithering
constexpr const char* BLUR_FRAGMENT = R"glsl(
#version 330 core
in vec2 vTexCoord;
out vec4 FragColor;
uniform sampler2D uTexture;
uniform vec2 uDirection; // (1,0) for horizontal, (0,1) for vertical
uniform vec2 uResolution; // Texture dimensions
uniform float uRadius; // Gaussian sigma in texels
// Screen-space hash for dithering (returns 0..1)
float hash12(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
void main()
{
vec2 texelSize = 1.0 / uResolution;
vec2 step = uDirection * texelSize;
// Dynamic Gaussian kernel: uRadius = sigma, extend to ~2.5 sigma
float sigma = max(uRadius, 0.5);
int iRadius = clamp(int(sigma * 2.5 + 0.5), 1, 40);
float inv2sigma2 = 1.0 / (2.0 * sigma * sigma);
vec3 sum = vec3(0.0);
float wSum = 0.0;
for (int i = -iRadius; i <= iRadius; i++) {
float w = exp(-float(i * i) * inv2sigma2);
sum += texture(uTexture, vTexCoord + step * float(i)).rgb * w;
wSum += w;
}
vec3 color = sum / wSum;
// Triangular-PDF dithering to break up 8-bit quantization banding.
// Two uniform hashes combined give a triangular distribution (-1..+1).
float d = hash12(gl_FragCoord.xy) + hash12(gl_FragCoord.xy + 71.37) - 1.0;
color += d / 255.0;
FragColor = vec4(color, 1.0);
}
)glsl";
} // namespace ShaderSource
} // namespace effects
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,302 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "framebuffer.h"
#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#include <cstdio>
#include "../../util/logger.h"
// GL 3.0 constants — may be missing from some GLAD configurations
#ifndef GL_RGBA16F
#define GL_RGBA16F 0x881A
#endif
#ifndef GL_HALF_FLOAT
#define GL_HALF_FLOAT 0x140B
#endif
namespace dragonx {
namespace ui {
namespace effects {
// ============================================================================
// Framebuffer Implementation (GLEW Available)
// ============================================================================
Framebuffer::Framebuffer() = default;
Framebuffer::~Framebuffer()
{
destroy();
}
Framebuffer::Framebuffer(Framebuffer&& other) noexcept
: fbo_(other.fbo_)
, colorTexture_(other.colorTexture_)
, depthRbo_(other.depthRbo_)
, width_(other.width_)
, height_(other.height_)
, isComplete_(other.isComplete_)
{
other.fbo_ = 0;
other.colorTexture_ = 0;
other.depthRbo_ = 0;
other.width_ = 0;
other.height_ = 0;
other.isComplete_ = false;
}
Framebuffer& Framebuffer::operator=(Framebuffer&& other) noexcept
{
if (this != &other) {
destroy();
fbo_ = other.fbo_;
colorTexture_ = other.colorTexture_;
depthRbo_ = other.depthRbo_;
width_ = other.width_;
height_ = other.height_;
isComplete_ = other.isComplete_;
other.fbo_ = 0;
other.colorTexture_ = 0;
other.depthRbo_ = 0;
other.width_ = 0;
other.height_ = 0;
other.isComplete_ = false;
}
return *this;
}
bool Framebuffer::init(int width, int height, bool useFloat16)
{
if (width <= 0 || height <= 0) {
DEBUG_LOGF("Framebuffer::init - Invalid dimensions: %dx%d\n", width, height);
return false;
}
// Clean up existing resources
cleanup();
width_ = width;
height_ = height;
useFloat16_ = useFloat16;
// Generate framebuffer
glGenFramebuffers(1, &fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
// Create color texture — use RGBA16F for blur intermediates to avoid banding
glGenTextures(1, &colorTexture_);
glBindTexture(GL_TEXTURE_2D, colorTexture_);
if (useFloat16) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_HALF_FLOAT, nullptr);
} else {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 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);
// Attach color texture to framebuffer
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture_, 0);
// Create depth renderbuffer (optional, but good for completeness)
glGenRenderbuffers(1, &depthRbo_);
glBindRenderbuffer(GL_RENDERBUFFER, depthRbo_);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRbo_);
// Check completeness
isComplete_ = checkComplete();
// Unbind
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
if (!isComplete_) {
DEBUG_LOGF("Framebuffer::init - Framebuffer is not complete!\n");
cleanup();
return false;
}
return true;
}
bool Framebuffer::resize(int width, int height)
{
if (width == width_ && height == height_) {
return true; // No change needed
}
return init(width, height, useFloat16_);
}
void Framebuffer::destroy()
{
cleanup();
}
void Framebuffer::cleanup()
{
if (colorTexture_) {
glDeleteTextures(1, &colorTexture_);
colorTexture_ = 0;
}
if (depthRbo_) {
glDeleteRenderbuffers(1, &depthRbo_);
depthRbo_ = 0;
}
if (fbo_) {
glDeleteFramebuffers(1, &fbo_);
fbo_ = 0;
}
width_ = 0;
height_ = 0;
isComplete_ = false;
}
bool Framebuffer::checkComplete()
{
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
const char* errorStr = "Unknown";
switch (status) {
case GL_FRAMEBUFFER_UNDEFINED: errorStr = "UNDEFINED"; break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: errorStr = "INCOMPLETE_ATTACHMENT"; break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: errorStr = "INCOMPLETE_MISSING_ATTACHMENT"; break;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: errorStr = "INCOMPLETE_DRAW_BUFFER"; break;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: errorStr = "INCOMPLETE_READ_BUFFER"; break;
case GL_FRAMEBUFFER_UNSUPPORTED: errorStr = "UNSUPPORTED"; break;
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: errorStr = "INCOMPLETE_MULTISAMPLE"; break;
case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: errorStr = "INCOMPLETE_LAYER_TARGETS"; break;
}
DEBUG_LOGF("Framebuffer error: %s (0x%X)\n", errorStr, status);
return false;
}
return true;
}
void Framebuffer::bind()
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
glViewport(0, 0, width_, height_);
}
void Framebuffer::unbind()
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void Framebuffer::clear(float r, float g, float b, float a)
{
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void Framebuffer::blitFrom(GLuint srcFbo, int srcX, int srcY, int srcWidth, int srcHeight,
int dstX, int dstY)
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_);
glBlitFramebuffer(
srcX, srcY, srcX + srcWidth, srcY + srcHeight, // Source rect
dstX, dstY, dstX + srcWidth, dstY + srcHeight, // Dest rect
GL_COLOR_BUFFER_BIT,
GL_LINEAR
);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void Framebuffer::captureScreen(int viewportWidth, int viewportHeight)
{
// Blit from default framebuffer (0) to this framebuffer
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_);
glBlitFramebuffer(
0, 0, viewportWidth, viewportHeight, // Source (screen)
0, 0, width_, height_, // Dest (this FBO)
GL_COLOR_BUFFER_BIT,
GL_LINEAR
);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
// ============================================================================
// FramebufferPingPong Implementation
// ============================================================================
bool FramebufferPingPong::init(int width, int height, bool useFloat16)
{
if (!buffers_[0].init(width, height, useFloat16)) return false;
if (!buffers_[1].init(width, height, useFloat16)) return false;
currentSource_ = 0;
return true;
}
bool FramebufferPingPong::resize(int width, int height)
{
if (!buffers_[0].resize(width, height)) return false;
if (!buffers_[1].resize(width, height)) return false;
return true;
}
void FramebufferPingPong::destroy()
{
buffers_[0].destroy();
buffers_[1].destroy();
currentSource_ = 0;
}
void FramebufferPingPong::swap()
{
currentSource_ = 1 - currentSource_;
}
} // namespace effects
} // namespace ui
} // namespace dragonx
#else // !DRAGONX_HAS_GLAD
// ============================================================================
// Stub Implementation (No GLAD - Acrylic effects disabled)
// ============================================================================
namespace dragonx {
namespace ui {
namespace effects {
Framebuffer::Framebuffer() = default;
Framebuffer::~Framebuffer() = default;
Framebuffer::Framebuffer(Framebuffer&&) noexcept = default;
Framebuffer& Framebuffer::operator=(Framebuffer&&) noexcept = default;
bool Framebuffer::init(int, int, bool) { return false; }
bool Framebuffer::resize(int, int) { return false; }
void Framebuffer::destroy() {}
void Framebuffer::cleanup() {}
bool Framebuffer::checkComplete() { return false; }
void Framebuffer::bind() {}
void Framebuffer::unbind() {}
void Framebuffer::clear(float, float, float, float) {}
void Framebuffer::blitFrom(unsigned int, int, int, int, int, int, int) {}
void Framebuffer::captureScreen(int, int) {}
bool FramebufferPingPong::init(int, int, bool) { return false; }
bool FramebufferPingPong::resize(int, int) { return false; }
void FramebufferPingPong::destroy() {}
void FramebufferPingPong::swap() {}
} // namespace effects
} // namespace ui
} // namespace dragonx
#endif // DRAGONX_HAS_GLAD

View File

@@ -0,0 +1,193 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
// GL type definitions needed by class declarations.
// If GLAD was already included, these types are already defined.
#ifndef GLAD_GL_H_
typedef unsigned int GLuint;
typedef int GLint;
typedef unsigned int GLenum;
typedef int GLsizei;
#endif
#include <cstdint>
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief OpenGL Framebuffer Object manager for off-screen rendering
*
* Used for capturing screen content to use as blur source for acrylic effects.
*/
class Framebuffer {
public:
Framebuffer();
~Framebuffer();
// Non-copyable
Framebuffer(const Framebuffer&) = delete;
Framebuffer& operator=(const Framebuffer&) = delete;
// Move constructors
Framebuffer(Framebuffer&& other) noexcept;
Framebuffer& operator=(Framebuffer&& other) noexcept;
/**
* @brief Initialize framebuffer with given dimensions
* @param width Width in pixels
* @param height Height in pixels
* @param useFloat16 Use RGBA16F format for higher precision (reduces banding)
* @return true if successful
*/
bool init(int width, int height, bool useFloat16 = false);
/**
* @brief Resize framebuffer (recreates internal textures)
* @param width New width
* @param height New height
* @return true if successful
*/
bool resize(int width, int height);
/**
* @brief Release all OpenGL resources
*/
void destroy();
/**
* @brief Bind this framebuffer for rendering
*/
void bind();
/**
* @brief Unbind (return to default framebuffer)
*/
void unbind();
/**
* @brief Clear the framebuffer
* @param r Red component (0-1)
* @param g Green component (0-1)
* @param b Blue component (0-1)
* @param a Alpha component (0-1)
*/
void clear(float r = 0.0f, float g = 0.0f, float b = 0.0f, float a = 1.0f);
/**
* @brief Get the color texture attached to this framebuffer
* @return OpenGL texture ID
*/
GLuint getColorTexture() const { return colorTexture_; }
/**
* @brief Get the underlying framebuffer object ID
* @return OpenGL FBO ID
*/
GLuint getFbo() const { return fbo_; }
/**
* @brief Get framebuffer dimensions
*/
int getWidth() const { return width_; }
int getHeight() const { return height_; }
/**
* @brief Check if framebuffer is valid and complete
*/
bool isValid() const { return fbo_ != 0 && isComplete_; }
/**
* @brief Copy a region from the current framebuffer to this one
* @param srcX Source X position
* @param srcY Source Y position
* @param srcWidth Source width
* @param srcHeight Source height
* @param dstX Destination X position
* @param dstY Destination Y position
*/
void blitFrom(GLuint srcFbo, int srcX, int srcY, int srcWidth, int srcHeight,
int dstX = 0, int dstY = 0);
/**
* @brief Copy entire default framebuffer to this one
* @param viewportWidth Current viewport width
* @param viewportHeight Current viewport height
*/
void captureScreen(int viewportWidth, int viewportHeight);
private:
void cleanup();
bool checkComplete();
GLuint fbo_ = 0;
GLuint colorTexture_ = 0;
GLuint depthRbo_ = 0; // Renderbuffer for depth (optional)
int width_ = 0;
int height_ = 0;
bool useFloat16_ = false; // RGBA16F for blur precision
bool isComplete_ = false;
};
/**
* @brief Manages multiple framebuffers for ping-pong blur operations
*/
class FramebufferPingPong {
public:
FramebufferPingPong() = default;
~FramebufferPingPong() = default;
/**
* @brief Initialize both framebuffers
*/
bool init(int width, int height, bool useFloat16 = false);
/**
* @brief Resize both framebuffers
*/
bool resize(int width, int height);
/**
* @brief Destroy both framebuffers
*/
void destroy();
/**
* @brief Swap which buffer is source/destination
*/
void swap();
/**
* @brief Get current source framebuffer
*/
Framebuffer& getSource() { return buffers_[currentSource_]; }
/**
* @brief Get current destination framebuffer
*/
Framebuffer& getDest() { return buffers_[1 - currentSource_]; }
/**
* @brief Get source texture for reading
*/
GLuint getSourceTexture() const { return buffers_[currentSource_].getColorTexture(); }
/**
* @brief Get destination texture
*/
GLuint getDestTexture() const { return buffers_[1 - currentSource_].getColorTexture(); }
bool isValid() const { return buffers_[0].isValid() && buffers_[1].isValid(); }
private:
Framebuffer buffers_[2];
int currentSource_ = 0;
};
} // namespace effects
} // namespace ui
} // namespace dragonx

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,381 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include "acrylic.h"
#include "imgui.h"
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief ImGui integration for acrylic effects
*
* Provides convenient wrappers around AcrylicMaterial for use with
* standard ImGui rendering patterns.
*/
namespace ImGuiAcrylic {
// ============================================================================
// Initialization
// ============================================================================
/**
* @brief Initialize the acrylic system
*
* Call once at application startup, after OpenGL context is created.
* @return true if successful
*/
bool Init();
/**
* @brief Shutdown the acrylic system
*
* Call at application shutdown before destroying OpenGL context.
*/
void Shutdown();
/**
* @brief Check if acrylic is available and initialized
*/
bool IsAvailable();
// ============================================================================
// Frame Operations
// ============================================================================
/**
* @brief Begin a new frame for acrylic rendering
*
* Call this at the start of your render loop, before any ImGui rendering.
* This captures the current screen content for blur source.
*
* @param width Viewport width
* @param height Viewport height
*/
void BeginFrame(int width, int height);
/**
* @brief End the acrylic frame
*
* Call this after all regular content is rendered but before acrylic overlays.
* Captures the current framebuffer content for blur.
*/
void CaptureBackground();
/**
* @brief Invalidate the acrylic capture so the next frame re-captures
* and re-blurs. Call after theme/skin changes that alter the
* background gradient, image, or colors.
*/
void InvalidateCapture();
/**
* @brief Get a draw callback for background capture.
*
* Insert this callback at the end of the BackgroundDrawList (after all
* background commands). During RenderDrawData() it fires right after
* the background is rasterized — before any UI windows — and blits
* the clean background into the acrylic capture buffer.
*
* Usage in main.cpp:
* @code
* bgDL->AddCallback(ImGuiAcrylic::GetBackgroundCaptureCallback(), nullptr);
* bgDL->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
* @endcode
*/
ImDrawCallback GetBackgroundCaptureCallback();
// ============================================================================
// Drawing Functions
// ============================================================================
/**
* @brief Draw an acrylic-filled rectangle
*
* @param drawList ImGui draw list to add the rectangle to
* @param pMin Top-left corner (screen coordinates)
* @param pMax Bottom-right corner (screen coordinates)
* @param params Acrylic appearance parameters
* @param rounding Corner rounding radius
*/
void DrawAcrylicRect(ImDrawList* drawList,
const ImVec2& pMin, const ImVec2& pMax,
const AcrylicParams& params,
float rounding = 0.0f);
/**
* @brief Draw an acrylic-filled rectangle with default preset
*/
void DrawAcrylicRect(ImDrawList* drawList,
const ImVec2& pMin, const ImVec2& pMax,
float rounding = 0.0f);
// ============================================================================
// Window Wrappers
// ============================================================================
/**
* @brief Begin an acrylic-background window
*
* Works like ImGui::Begin but with acrylic background.
* Must be paired with EndAcrylicWindow().
*
* @param name Window name/ID
* @param p_open Optional close button flag
* @param flags ImGui window flags
* @param params Acrylic appearance parameters
* @return true if window is visible
*/
bool BeginAcrylicWindow(const char* name, bool* p_open = nullptr,
ImGuiWindowFlags flags = 0,
const AcrylicParams& params = AcrylicMaterial::getPopupPreset());
/**
* @brief End an acrylic window
*/
void EndAcrylicWindow();
// ============================================================================
// Child Region Wrappers
// ============================================================================
/**
* @brief Begin an acrylic child region
*
* Works like ImGui::BeginChild but with acrylic background.
* Must be paired with EndAcrylicChild().
*
* @param str_id Child region ID
* @param size Child region size (0,0 = auto)
* @param params Acrylic appearance parameters
* @param child_flags ImGui child flags
* @param window_flags ImGui window flags for the child
* @return true if child is visible
*/
bool BeginAcrylicChild(const char* str_id,
const ImVec2& size = ImVec2(0, 0),
const AcrylicParams& params = AcrylicMaterial::getDarkPreset(),
ImGuiChildFlags child_flags = 0,
ImGuiWindowFlags window_flags = 0);
/**
* @brief End an acrylic child region
*/
void EndAcrylicChild();
// ============================================================================
// Popup Wrappers
// ============================================================================
/**
* @brief Begin an acrylic popup
*
* Works like ImGui::BeginPopup but with acrylic background.
* Must be paired with EndAcrylicPopup().
*
* @param str_id Popup ID
* @param flags ImGui window flags
* @param params Acrylic appearance parameters
* @return true if popup is open
*/
bool BeginAcrylicPopup(const char* str_id,
ImGuiWindowFlags flags = 0,
const AcrylicParams& params = AcrylicMaterial::getPopupPreset());
/**
* @brief End an acrylic popup
*/
void EndAcrylicPopup();
/**
* @brief Begin an acrylic modal popup
*
* Works like ImGui::BeginPopupModal but with acrylic background.
* Must be paired with EndAcrylicPopup().
*/
bool BeginAcrylicPopupModal(const char* name, bool* p_open = nullptr,
ImGuiWindowFlags flags = 0,
const AcrylicParams& params = AcrylicMaterial::getPopupPreset());
// ============================================================================
// Context Menu Helpers
// ============================================================================
/**
* @brief Begin an acrylic context menu for the last item
*
* Works like ImGui::BeginPopupContextItem but with acrylic background.
* Must be paired with EndAcrylicPopup().
*
* @param str_id Popup ID (nullptr to use last item ID)
* @param popup_flags ImGui popup flags
* @param params Acrylic appearance parameters
* @return true if context menu is open
*/
bool BeginAcrylicContextItem(const char* str_id = nullptr,
ImGuiPopupFlags popup_flags = 0,
const AcrylicParams& params = AcrylicMaterial::getPopupPreset());
/**
* @brief Begin an acrylic context menu for the current window
*
* Works like ImGui::BeginPopupContextWindow but with acrylic background.
* Must be paired with EndAcrylicPopup().
*/
bool BeginAcrylicContextWindow(const char* str_id = nullptr,
ImGuiPopupFlags popup_flags = 0,
const AcrylicParams& params = AcrylicMaterial::getPopupPreset());
// ============================================================================
// Sidebar Helper
// ============================================================================
/**
* @brief Draw an acrylic sidebar background
*
* Convenience function for drawing sidebar with DragonX branded acrylic.
*
* @param width Sidebar width
* @param params Optional custom parameters (default: sidebar preset)
*/
void DrawSidebarBackground(float width,
const AcrylicParams& params = AcrylicMaterial::getSidebarPreset());
// ============================================================================
// Settings
// ============================================================================
/**
* @brief Set global acrylic quality
*/
void SetQuality(AcrylicQuality quality);
/**
* @brief Get current quality setting
*/
AcrylicQuality GetQuality();
/**
* @brief Enable/disable acrylic globally
*
* When disabled, all acrylic functions fall back to solid colors.
*/
void SetEnabled(bool enabled);
/**
* @brief Check if acrylic is enabled
*/
bool IsEnabled();
/**
* @brief Set blur radius multiplier (scales all blur radii)
* @param multiplier Value from 0.5 to 2.0 (1.0 = default)
*/
void SetBlurMultiplier(float multiplier);
/**
* @brief Get current blur multiplier
*/
float GetBlurMultiplier();
/**
* @brief Set reduced transparency mode (accessibility)
*
* When enabled, uses solid fallback colors instead of blur effects.
*/
void SetReducedTransparency(bool reduced);
/**
* @brief Check if reduced transparency mode is active
*/
bool GetReducedTransparency();
/**
* @brief Set UI opacity multiplier for cards/sidebar (0.31.0, 1=opaque)
*/
void SetUIOpacity(float opacity);
/**
* @brief Get UI opacity multiplier
*/
float GetUIOpacity();
/**
* @brief Set noise opacity multiplier (0.0 = no noise, 1.0 = default, 2.0 = max)
*/
void SetNoiseOpacity(float multiplier);
/**
* @brief Get noise opacity multiplier
*/
float GetNoiseOpacity();
/**
* @brief Get full acrylic settings
*/
const AcrylicSettings& GetSettings();
/**
* @brief Set full acrylic settings
*/
void SetSettings(const AcrylicSettings& settings);
// ============================================================================
// Presets — combined quality + blur amount
// ============================================================================
/// Number of built-in presets (Off … Frosted)
constexpr int kPresetCount = 6;
/**
* @brief Apply a preset that sets both quality and blur multiplier.
* @param preset Index 05 (Off, Subtle, Light, Standard, Strong, Frosted)
*/
void SetPreset(int preset);
/**
* @brief Derive the closest preset index from current quality + blur.
*/
int GetPreset();
/**
* @brief Human-readable label for a preset index.
*/
const char* GetPresetLabel(int preset);
/**
* @brief Get the quality enum for a given preset index.
*/
AcrylicQuality PresetQuality(int preset);
/**
* @brief Get the blur multiplier for a given preset index.
*/
float PresetBlur(int preset);
/**
* @brief Find closest preset matching a quality + blur pair.
* Useful for migrating from legacy separate settings.
*/
int MatchPreset(AcrylicQuality quality, float blur);
/**
* @brief Apply a continuous blur amount.
*
* Uses Low quality (like the old "Subtle" preset) at any non-zero blur
* level, giving fine-grained control without stepped presets.
* When blur is near zero the effect is disabled entirely.
*
* @param blur Blur multiplier (0.0 = off, up to 4.0 = maximum)
*/
void ApplyBlurAmount(float blur);
} // namespace ImGuiAcrylic
} // namespace effects
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,24 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "low_spec.h"
#include <atomic>
namespace dragonx {
namespace ui {
namespace effects {
static std::atomic<bool> s_low_spec{false};
bool isLowSpecMode() {
return s_low_spec.load(std::memory_order_relaxed);
}
void setLowSpecMode(bool enabled) {
s_low_spec.store(enabled, std::memory_order_relaxed);
}
} // namespace effects
} // namespace ui
} // namespace dragonx

21
src/ui/effects/low_spec.h Normal file
View File

@@ -0,0 +1,21 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
namespace dragonx {
namespace ui {
namespace effects {
/// Global low-spec mode query. When enabled, heavy visual effects are
/// skipped: glass panel noise tiling, elevation shadow layers, ripple
/// circle expansion, viewport noise overlay, scroll-edge fade shader,
/// and mining pulse animations. Individual effect toggles (acrylic,
/// theme effects, scanline) are overridden via the settings page.
bool isLowSpecMode();
void setLowSpecMode(bool enabled);
} // namespace effects
} // namespace ui
} // namespace dragonx

View File

@@ -0,0 +1,354 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "noise_texture.h"
#include "../../util/logger.h"
#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#include <cstdio>
#include <cmath>
#include <vector>
#include <cstring>
namespace dragonx {
namespace ui {
namespace effects {
// ============================================================================
// NoiseTexture Implementation (GLEW Available)
// ============================================================================
NoiseTexture::NoiseTexture() = default;
NoiseTexture::~NoiseTexture()
{
destroy();
}
float NoiseTexture::random(uint32_t& seed)
{
// Xorshift32 PRNG
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return static_cast<float>(seed) / static_cast<float>(0xFFFFFFFF);
}
float NoiseTexture::noise2D(float x, float y, int wrap)
{
// Simple value noise with smoothstep interpolation
int xi = static_cast<int>(std::floor(x)) & (wrap - 1);
int yi = static_cast<int>(std::floor(y)) & (wrap - 1);
float xf = x - std::floor(x);
float yf = y - std::floor(y);
// Smoothstep
float u = xf * xf * (3.0f - 2.0f * xf);
float v = yf * yf * (3.0f - 2.0f * yf);
// Hash function for corners
auto hash = [wrap](int x, int y) -> float {
uint32_t seed = static_cast<uint32_t>((x & (wrap-1)) * 374761393 + (y & (wrap-1)) * 668265263);
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return static_cast<float>(seed) / static_cast<float>(0xFFFFFFFF);
};
// Bilinear interpolation
float n00 = hash(xi, yi);
float n10 = hash(xi + 1, yi);
float n01 = hash(xi, yi + 1);
float n11 = hash(xi + 1, yi + 1);
float nx0 = n00 * (1.0f - u) + n10 * u;
float nx1 = n01 * (1.0f - u) + n11 * u;
return nx0 * (1.0f - v) + nx1 * v;
}
bool NoiseTexture::generate(int size, float intensity)
{
destroy();
if (size <= 0 || (size & (size - 1)) != 0) {
DEBUG_LOGF("NoiseTexture: Size must be power of 2, got %d\n", size);
return false;
}
size_ = size;
// Generate pure white noise — no coherent patterns, perfectly seamless
// when tiled because every pixel is independent.
std::vector<uint8_t> data(size * size * 4);
uint32_t seed = 12345;
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
float r = random(seed);
// Apply intensity around mid-gray center
float value = 0.5f + (r - 0.5f) * intensity;
value = std::max(0.0f, std::min(1.0f, value));
uint8_t gray = static_cast<uint8_t>(value * 255.0f);
int idx = (y * size + x) * 4;
data[idx + 0] = gray;
data[idx + 1] = gray;
data[idx + 2] = gray;
data[idx + 3] = 255;
}
}
// Create OpenGL texture
glGenTextures(1, &texture_);
glBindTexture(GL_TEXTURE_2D, texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
// NEAREST filtering preserves individual grain pixels;
// REPEAT wrapping ensures seamless tiling.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
DEBUG_LOGF("NoiseTexture: Generated %dx%d white noise texture (intensity=%.2f)\n", size, size, intensity);
return texture_ != 0;
}
void NoiseTexture::destroy()
{
if (texture_) {
glDeleteTextures(1, &texture_);
texture_ = 0;
}
size_ = 0;
}
// ============================================================================
// NoiseUtils Implementation
// ============================================================================
namespace NoiseUtils {
unsigned int createWhiteNoiseTexture(int size)
{
std::vector<uint8_t> data(size * size * 4);
uint32_t seed = 42;
for (int i = 0; i < size * size; i++) {
// Simple white noise
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
uint8_t value = static_cast<uint8_t>((seed >> 24) & 0xFF);
data[i * 4 + 0] = value;
data[i * 4 + 1] = value;
data[i * 4 + 2] = value;
data[i * 4 + 3] = 255;
}
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
unsigned int createBlueNoiseTexture(int size)
{
// Blue noise has more even distribution - good for dithering
// Using a simple void-and-cluster algorithm approximation
std::vector<float> noise(size * size, 0.5f);
std::vector<uint8_t> data(size * size * 4);
uint32_t seed = 12345;
// Start with white noise
for (int i = 0; i < size * size; i++) {
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
noise[i] = static_cast<float>(seed) / static_cast<float>(0xFFFFFFFF);
}
// Apply a simple high-pass filter to reduce low frequencies
// (Very simplified blue noise approximation)
std::vector<float> filtered(size * size);
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
float center = noise[y * size + x];
float sum = 0.0f;
int count = 0;
// Sample neighbors
for (int dy = -2; dy <= 2; dy++) {
for (int dx = -2; dx <= 2; dx++) {
if (dx == 0 && dy == 0) continue;
int nx = (x + dx + size) % size;
int ny = (y + dy + size) % size;
sum += noise[ny * size + nx];
count++;
}
}
float avg = sum / count;
filtered[y * size + x] = 0.5f + (center - avg) * 2.0f;
}
}
// Convert to texture data
for (int i = 0; i < size * size; i++) {
float value = std::max(0.0f, std::min(1.0f, filtered[i]));
uint8_t gray = static_cast<uint8_t>(value * 255.0f);
data[i * 4 + 0] = gray;
data[i * 4 + 1] = gray;
data[i * 4 + 2] = gray;
data[i * 4 + 3] = 255;
}
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
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_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
unsigned int createAcrylicNoiseTexture(int size)
{
// Specially tuned noise for Microsoft Acrylic material look
// Very subtle, with a hint of blue-ish tint
std::vector<uint8_t> data(size * size * 4);
uint32_t seed = 98765;
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
// Multiple noise layers
float n1 = NoiseTexture::noise2D(x * 0.5f, y * 0.5f, size);
float n2 = NoiseTexture::noise2D(x * 1.0f, y * 1.0f, size) * 0.5f;
float n3 = NoiseTexture::noise2D(x * 2.0f, y * 2.0f, size) * 0.25f;
// White noise component
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
float white = static_cast<float>(seed) / static_cast<float>(0xFFFFFFFF);
// Combine
float combined = (n1 + n2 + n3) / 1.75f * 0.6f + white * 0.4f;
// Very subtle intensity (0.02 - 0.04 typical for acrylic)
float value = 0.5f + (combined - 0.5f) * 0.04f;
value = std::max(0.0f, std::min(1.0f, value));
uint8_t gray = static_cast<uint8_t>(value * 255.0f);
// Slight cool tint (very subtle)
int idx = (y * size + x) * 4;
data[idx + 0] = static_cast<uint8_t>(gray * 0.98f); // R slightly less
data[idx + 1] = gray; // G
data[idx + 2] = static_cast<uint8_t>(std::min(255, gray + 1)); // B slightly more
data[idx + 3] = 255;
}
}
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
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_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
} // namespace NoiseUtils
} // namespace effects
} // namespace ui
} // namespace dragonx
#else // !DRAGONX_HAS_GLAD
// ============================================================================
// Stub Implementation (No GLAD - Acrylic effects disabled)
// ============================================================================
#include <cmath>
#include <cstdint>
namespace dragonx {
namespace ui {
namespace effects {
NoiseTexture::NoiseTexture() = default;
NoiseTexture::~NoiseTexture() = default;
float NoiseTexture::random(uint32_t& seed)
{
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return static_cast<float>(seed) / static_cast<float>(0xFFFFFFFF);
}
float NoiseTexture::noise2D(float x, float y, int wrap)
{
// Provide a basic implementation for potential use elsewhere
int xi = static_cast<int>(std::floor(x)) & (wrap - 1);
int yi = static_cast<int>(std::floor(y)) & (wrap - 1);
uint32_t seed = static_cast<uint32_t>(xi * 374761393 + yi * 668265263);
return random(seed);
}
bool NoiseTexture::generate(int, float) { return false; }
void NoiseTexture::destroy() {}
namespace NoiseUtils {
unsigned int createWhiteNoiseTexture(int) { return 0; }
unsigned int createBlueNoiseTexture(int) { return 0; }
unsigned int createAcrylicNoiseTexture(int) { return 0; }
} // namespace NoiseUtils
} // namespace effects
} // namespace ui
} // namespace dragonx
#endif // DRAGONX_HAS_GLAD

View File

@@ -0,0 +1,113 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#ifndef GLAD_GL_H_
typedef unsigned int GLuint;
typedef int GLint;
typedef unsigned int GLenum;
typedef int GLsizei;
#endif
#include <cstdint>
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief Generates procedural noise textures for acrylic grain effect
*
* Creates a tileable noise texture that adds subtle grain to acrylic surfaces,
* matching Microsoft's Fluent Design acrylic material.
*/
class NoiseTexture {
public:
NoiseTexture();
~NoiseTexture();
// Non-copyable
NoiseTexture(const NoiseTexture&) = delete;
NoiseTexture& operator=(const NoiseTexture&) = delete;
/**
* @brief Generate a noise texture
* @param size Texture size (width = height, should be power of 2)
* @param intensity Noise intensity (0.0 - 1.0, typical: 0.02 - 0.04)
* @return true if successful
*/
bool generate(int size = 128, float intensity = 0.04f);
/**
* @brief Release texture resources
*/
void destroy();
/**
* @brief Get the OpenGL texture ID
*/
GLuint getTexture() const { return texture_; }
/**
* @brief Check if texture is valid
*/
bool isValid() const { return texture_ != 0; }
/**
* @brief Get texture size
*/
int getSize() const { return size_; }
/**
* @brief Simple pseudo-random number generator
* @param seed Seed value (modified in place)
* @return Random float in range [0, 1]
*/
static float random(uint32_t& seed);
/**
* @brief Generate tileable Perlin-style noise value
*/
static float noise2D(float x, float y, int wrap);
private:
GLuint texture_ = 0;
int size_ = 0;
};
/**
* @brief Static utility functions for noise generation
*/
namespace NoiseUtils {
/**
* @brief Create a simple white noise texture
* @param size Texture dimensions (square)
* @return OpenGL texture ID (0 on failure)
*/
GLuint createWhiteNoiseTexture(int size);
/**
* @brief Create a blue noise texture (better for dithering)
* @param size Texture dimensions (square)
* @return OpenGL texture ID (0 on failure)
*/
GLuint createBlueNoiseTexture(int size);
/**
* @brief Create a pre-computed acrylic noise texture
*
* This creates a specially tuned noise texture that matches
* Microsoft's acrylic material appearance.
*
* @param size Texture dimensions (128 recommended)
* @return OpenGL texture ID (0 on failure)
*/
GLuint createAcrylicNoiseTexture(int size = 128);
} // namespace NoiseUtils
} // namespace effects
} // namespace ui
} // namespace dragonx

View 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

View File

@@ -0,0 +1,416 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
// Shader-based scroll-edge fade effect.
//
// Inserts ImDrawCallback entries into the draw list to switch from ImGui's
// default shader to a custom shader that multiplies output alpha by a
// smoothstep gradient based on screen-Y position. After the fade region,
// ImDrawCallback_ResetRenderState restores ImGui's default render state.
//
// GL path: custom GLSL 130 program (VS + FS), uses gl_FragCoord.y
// DX11 path: custom pixel shader only, uses SV_POSITION.y
#pragma once
#include "imgui.h"
#include "imgui_internal.h"
// ---- Platform headers ----
#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#endif
#ifdef DRAGONX_USE_DX11
#include <d3d11.h>
#include <d3dcompiler.h>
#endif
#include <cstdio>
#include <cstring>
#include <cstddef>
#include "../../util/logger.h"
namespace dragonx {
namespace ui {
namespace effects {
// ============================================================================
// ScrollFadeShader — per-pixel scroll-edge alpha mask via ImDrawCallback
// ============================================================================
struct ScrollFadeShader {
// ---- Caller-set parameters (logical screen-space, per-frame) ----
float fadeTopY = 0.0f; // top of scrollable region (screen Y)
float fadeBottomY = 0.0f; // bottom of scrollable region (screen Y)
float fadeZoneTop = 0.0f; // top fade height (0 = disabled)
float fadeZoneBottom = 0.0f; // bottom fade height (0 = disabled)
// ---- Internal state ----
bool ready = false;
#ifdef DRAGONX_HAS_GLAD
GLuint program_ = 0;
GLint locProjMtx_ = -1;
GLint locTexture_ = -1;
GLint locFadeTopY_ = -1;
GLint locFadeBottomY_= -1;
GLint locFadeZoneTop_= -1;
GLint locFadeZoneBot_= -1;
GLint locViewportH_ = -1;
#endif
#ifdef DRAGONX_USE_DX11
ID3D11PixelShader* pFadePS_ = nullptr;
ID3D11Buffer* pFadeCB_ = nullptr;
#endif
// ----------------------------------------------------------------
// Init / Destroy
// ----------------------------------------------------------------
bool init() {
if (ready) return true;
#ifdef DRAGONX_HAS_GLAD
ready = initGL();
#elif defined(DRAGONX_USE_DX11)
ready = initDX11();
#endif
return ready;
}
void destroy() {
#ifdef DRAGONX_HAS_GLAD
destroyGL();
#elif defined(DRAGONX_USE_DX11)
destroyDX11();
#endif
ready = false;
}
// ----------------------------------------------------------------
// Draw-list integration helpers
// ----------------------------------------------------------------
/// Insert a "bind fade shader" callback into the draw list.
/// Must be followed later by addUnbind() or AddCallback(ImDrawCallback_ResetRenderState).
void addBind(ImDrawList* dl) {
dl->AddCallback(BindCB, this);
}
/// Insert ImDrawCallback_ResetRenderState to restore ImGui's default shader.
static void addUnbind(ImDrawList* dl) {
dl->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
}
// ================================================================
// GL implementation
// ================================================================
#ifdef DRAGONX_HAS_GLAD
static constexpr const char* kFadeVS_130 =
"#version 130\n"
"uniform mat4 ProjMtx;\n"
"in vec2 Position;\n"
"in vec2 UV;\n"
"in vec4 Color;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main() {\n"
" Frag_UV = UV;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n"
"}\n";
static constexpr const char* kFadeFS_130 =
"#version 130\n"
"uniform sampler2D Texture;\n"
"uniform float u_fadeTopY;\n"
"uniform float u_fadeBottomY;\n"
"uniform float u_fadeZoneTop;\n"
"uniform float u_fadeZoneBot;\n"
"uniform float u_viewportH;\n"
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"out vec4 Out_Color;\n"
"void main() {\n"
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
" float y = u_viewportH - gl_FragCoord.y;\n" // bottom-up → top-down
" float topA = (u_fadeZoneTop > 0.0)\n"
" ? smoothstep(u_fadeTopY, u_fadeTopY + u_fadeZoneTop, y)\n"
" : 1.0;\n"
" float botA = (u_fadeZoneBot > 0.0)\n"
" ? (1.0 - smoothstep(u_fadeBottomY - u_fadeZoneBot, u_fadeBottomY, y))\n"
" : 1.0;\n"
" Out_Color.a *= topA * botA;\n"
"}\n";
bool initGL() {
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vs, 1, &kFadeVS_130, nullptr);
glCompileShader(vs);
if (!checkShader(vs, "fade VS")) { glDeleteShader(vs); glDeleteShader(fs); return false; }
glShaderSource(fs, 1, &kFadeFS_130, nullptr);
glCompileShader(fs);
if (!checkShader(fs, "fade FS")) { glDeleteShader(vs); glDeleteShader(fs); return false; }
program_ = glCreateProgram();
glAttachShader(program_, vs);
glAttachShader(program_, fs);
// Force attribute locations to match ImGui's typical assignment
// (Position=0, UV=1, Color=2) so that the VAO set up by ImGui's
// backend is compatible with our program.
glBindAttribLocation(program_, 0, "Position");
glBindAttribLocation(program_, 1, "UV");
glBindAttribLocation(program_, 2, "Color");
glLinkProgram(program_);
glDetachShader(program_, vs);
glDetachShader(program_, fs);
glDeleteShader(vs);
glDeleteShader(fs);
if (!checkProgram(program_, "fade program")) {
glDeleteProgram(program_);
program_ = 0;
return false;
}
locProjMtx_ = glGetUniformLocation(program_, "ProjMtx");
locTexture_ = glGetUniformLocation(program_, "Texture");
locFadeTopY_ = glGetUniformLocation(program_, "u_fadeTopY");
locFadeBottomY_ = glGetUniformLocation(program_, "u_fadeBottomY");
locFadeZoneTop_ = glGetUniformLocation(program_, "u_fadeZoneTop");
locFadeZoneBot_ = glGetUniformLocation(program_, "u_fadeZoneBot");
locViewportH_ = glGetUniformLocation(program_, "u_viewportH");
DEBUG_LOGF("ScrollFadeShader: GL program %u (ProjMtx=%d Tex=%d top=%d bot=%d zt=%d zb=%d vh=%d)\n",
program_, locProjMtx_, locTexture_,
locFadeTopY_, locFadeBottomY_, locFadeZoneTop_, locFadeZoneBot_, locViewportH_);
return true;
}
void destroyGL() {
if (program_) { glDeleteProgram(program_); program_ = 0; }
}
static bool checkShader(GLuint s, const char* label) {
GLint ok = 0;
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[512];
glGetShaderInfoLog(s, sizeof(log), nullptr, log);
DEBUG_LOGF("ScrollFadeShader: %s compile error:\n%s\n", label, log);
}
return ok != 0;
}
static bool checkProgram(GLuint p, const char* label) {
GLint ok = 0;
glGetProgramiv(p, GL_LINK_STATUS, &ok);
if (!ok) {
char log[512];
glGetProgramInfoLog(p, sizeof(log), nullptr, log);
DEBUG_LOGF("ScrollFadeShader: %s link error:\n%s\n", label, log);
}
return ok != 0;
}
#endif // DRAGONX_HAS_GLAD
// ================================================================
// DX11 implementation
// ================================================================
#ifdef DRAGONX_USE_DX11
// Pixel-shader constant buffer layout (must be 16-byte aligned)
struct alignas(16) FadeCBData {
float fadeTopY;
float fadeBottomY;
float fadeZoneTop;
float fadeZoneBot;
};
static constexpr const char* kFadePS_HLSL =
"cbuffer FadeParams : register(b1) {\n"
" float u_fadeTopY;\n"
" float u_fadeBottomY;\n"
" float u_fadeZoneTop;\n"
" float u_fadeZoneBot;\n"
"};\n"
"struct PS_INPUT {\n"
" float4 pos : SV_POSITION;\n"
" float4 col : COLOR0;\n"
" float2 uv : TEXCOORD0;\n"
"};\n"
"sampler sampler0;\n"
"Texture2D texture0;\n"
"float4 main(PS_INPUT input) : SV_Target {\n"
" float4 out_col = input.col * texture0.Sample(sampler0, input.uv);\n"
" float y = input.pos.y;\n" // SV_POSITION.y is top-down in FB pixels
" float topA = (u_fadeZoneTop > 0.0)\n"
" ? smoothstep(u_fadeTopY, u_fadeTopY + u_fadeZoneTop, y)\n"
" : 1.0;\n"
" float botA = (u_fadeZoneBot > 0.0)\n"
" ? (1.0 - smoothstep(u_fadeBottomY - u_fadeZoneBot, u_fadeBottomY, y))\n"
" : 1.0;\n"
" out_col.a *= topA * botA;\n"
" return out_col;\n"
"}\n";
bool initDX11() {
// Get DX11 device from ImGui backend
auto* bd = ImGui::GetIO().BackendRendererUserData;
if (!bd) { DEBUG_LOGF("ScrollFadeShader: no DX11 backend data\n"); return false; }
// ImGui_ImplDX11_Data layout: first field is ID3D11Device*
ID3D11Device* device = *(ID3D11Device**)bd;
if (!device) { DEBUG_LOGF("ScrollFadeShader: null DX11 device\n"); return false; }
// Compile pixel shader
ID3DBlob* psBlob = nullptr;
ID3DBlob* errBlob = nullptr;
HRESULT hr = D3DCompile(kFadePS_HLSL, strlen(kFadePS_HLSL),
nullptr, nullptr, nullptr,
"main", "ps_4_0", 0, 0,
&psBlob, &errBlob);
if (FAILED(hr)) {
if (errBlob) {
DEBUG_LOGF("ScrollFadeShader: PS compile error:\n%s\n",
(const char*)errBlob->GetBufferPointer());
errBlob->Release();
}
return false;
}
if (errBlob) errBlob->Release();
hr = device->CreatePixelShader(psBlob->GetBufferPointer(),
psBlob->GetBufferSize(),
nullptr, &pFadePS_);
psBlob->Release();
if (FAILED(hr)) {
DEBUG_LOGF("ScrollFadeShader: CreatePixelShader failed 0x%08lx\n", hr);
return false;
}
// Create constant buffer for fade params
D3D11_BUFFER_DESC cbDesc = {};
cbDesc.ByteWidth = sizeof(FadeCBData);
cbDesc.Usage = D3D11_USAGE_DYNAMIC;
cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
hr = device->CreateBuffer(&cbDesc, nullptr, &pFadeCB_);
if (FAILED(hr)) {
DEBUG_LOGF("ScrollFadeShader: CreateBuffer (CB) failed 0x%08lx\n", hr);
pFadePS_->Release(); pFadePS_ = nullptr;
return false;
}
DEBUG_LOGF("ScrollFadeShader: DX11 pixel shader + CB created\n");
return true;
}
void destroyDX11() {
if (pFadeCB_) { pFadeCB_->Release(); pFadeCB_ = nullptr; }
if (pFadePS_) { pFadePS_->Release(); pFadePS_ = nullptr; }
}
#endif // DRAGONX_USE_DX11
// ================================================================
// Callback — called by ImGui's render backend during RenderDrawData
// ================================================================
static void BindCB(const ImDrawList* /*dl*/, const ImDrawCmd* cmd) {
auto* self = static_cast<ScrollFadeShader*>(cmd->UserCallbackData);
if (!self || !self->ready) return;
ImGuiIO& io = ImGui::GetIO();
float fbScaleY = io.DisplayFramebufferScale.y;
float fb_height = io.DisplaySize.y * fbScaleY;
// In multi-viewport mode ImGui coordinates are OS-absolute, but
// SV_POSITION / gl_FragCoord are render-target-relative. Subtract
// the main viewport origin to convert screen-space → RT-local.
float vpY = ImGui::GetMainViewport()->Pos.y;
// Convert logical screen-space params to framebuffer pixels
float topY = (self->fadeTopY - vpY) * fbScaleY;
float botY = (self->fadeBottomY - vpY) * fbScaleY;
float zoneTop = self->fadeZoneTop * fbScaleY;
float zoneBot = self->fadeZoneBottom * fbScaleY;
#ifdef DRAGONX_HAS_GLAD
glUseProgram(self->program_);
// Rebind vertex attribute pointers so our forced locations (0=Position,
// 1=UV, 2=Color) match the already-bound VBO data. ImGui's linker may
// assign different locations (e.g. Mesa uses alphabetical order:
// Color=0, Position=1, UV=2), but the VBO layout is fixed, so we
// must explicitly point our attributes at the correct offsets.
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos));
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv));
glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
// Ortho projection — must match ImGui's SetupRenderState which uses
// draw_data->DisplayPos as the origin. With ViewportsEnable, vertex
// positions are in OS screen-space; the projection matrix maps them
// to window-local framebuffer coordinates.
float vpX = ImGui::GetMainViewport()->Pos.x;
float L = vpX;
float R = vpX + io.DisplaySize.x;
float T = vpY;
float B = vpY + io.DisplaySize.y;
const float ortho[4][4] = {
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
{ 0.0f, 0.0f, -1.0f, 0.0f },
{ (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f },
};
glUniformMatrix4fv(self->locProjMtx_, 1, GL_FALSE, &ortho[0][0]);
glUniform1i(self->locTexture_, 0);
glUniform1f(self->locFadeTopY_, topY);
glUniform1f(self->locFadeBottomY_, botY);
glUniform1f(self->locFadeZoneTop_, zoneTop);
glUniform1f(self->locFadeZoneBot_, zoneBot);
glUniform1f(self->locViewportH_, fb_height);
#endif
#ifdef DRAGONX_USE_DX11
(void)fb_height;
// Get device context from ImGui backend (first two fields of ImGui_ImplDX11_Data)
auto* bd = io.BackendRendererUserData;
if (!bd) return;
struct DX11BD { ID3D11Device* dev; ID3D11DeviceContext* ctx; };
auto* dx = reinterpret_cast<DX11BD*>(bd);
ID3D11DeviceContext* ctx = dx->ctx;
if (!ctx) return;
// Update constant buffer
D3D11_MAPPED_SUBRESOURCE mapped;
if (SUCCEEDED(ctx->Map(self->pFadeCB_, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) {
auto* cb = static_cast<FadeCBData*>(mapped.pData);
cb->fadeTopY = topY;
cb->fadeBottomY = botY;
cb->fadeZoneTop = zoneTop;
cb->fadeZoneBot = zoneBot;
ctx->Unmap(self->pFadeCB_, 0);
}
// Bind our pixel shader + constant buffer (slot 1 = register(b1))
ctx->PSSetShader(self->pFadePS_, nullptr, 0);
ctx->PSSetConstantBuffers(1, 1, &self->pFadeCB_);
#endif
}
};
} // namespace effects
} // namespace ui
} // namespace dragonx

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,244 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <imgui.h>
#include <vector>
#include <string>
namespace dragonx {
namespace ui {
namespace effects {
/**
* @brief Per-theme visual effects system.
*
* Draws animated accents (hue-cycling, rainbow borders, shimmer sweeps,
* glow pulses, positional tinting) driven entirely by TOML theme config.
* All effects use ImDrawList primitives — no shaders or texture uploads.
*/
class ThemeEffects {
public:
static ThemeEffects& instance();
/// Called once per frame (timing updates)
void beginFrame();
/// Master toggle (from settings)
void setEnabled(bool enabled) { enabled_ = enabled; }
bool isEnabled() const { return enabled_; }
/// Background opacity (from window opacity slider). Multiplies
/// all effect alphas so they fade with the backdrop while UI stays opaque.
void setBackgroundOpacity(float o) { bgOpacity_ = std::max(0.0f, std::min(1.0f, o)); }
float backgroundOpacity() const { return bgOpacity_; }
/// Reduced transparency suppresses shimmer + rainbow border
void setReducedTransparency(bool rt) { reduced_transparency_ = rt; }
bool isReducedTransparency() const { return reduced_transparency_; }
/// Reload [effects] config from the active UISchema overlay
void loadFromTheme();
// === Drawing APIs ===
/// Get the current hue-cycled accent color (optionally phase-shifted)
ImU32 getAccentColor(float phaseOffset = 0.0f) const;
/// Draw rainbow gradient border around a rect (for glass panels)
void drawRainbowBorder(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding, float thickness) const;
/// Draw shimmer sweep over a rect
void drawShimmer(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
/// Get positional hue tint for a given screen Y
ImU32 getPositionalTint(float screenY) const;
/// Tint an existing color by positional hue
ImU32 tintByPosition(ImU32 baseColor, float screenY) const;
/// Draw glow pulse behind a rect (for active elements)
void drawGlowPulse(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
/// Draw a light that traces along the border perimeter
void drawEdgeTrace(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
/// Draw a border that shifts between two colors over time (gem-like)
void drawGradientBorderShift(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
/// Draw ember particles that rise from an element (fire theme)
void drawEmberRise(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax) const;
/// Draw specular glare highlights on a panel (polished surface look)
void drawSpecularGlare(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
/// Draw viewport-wide ambient ember particles (fire theme atmosphere)
void drawViewportEmbers(ImDrawList* dl) const;
/// Draw viewport-wide sandstorm particles (wind-driven diagonal sand/dust)
void drawSandstorm(ImDrawList* dl) const;
/// Draw shader-like viewport overlay (color wash + vignette post-processing)
void drawViewportOverlay(ImDrawList* dl) const;
/// Draw all applicable panel effects (edge trace + ember rise on glass panels)
/// phaseKey provides per-panel phase diversity based on panel position
void drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float rounding) const;
// === Query which effects are available ===
bool hasRainbowBorder() const { return enabled_ && !reduced_transparency_ && rainbow_border_.enabled; }
bool hasShimmer() const { return enabled_ && !reduced_transparency_ && shimmer_.enabled; }
bool hasGlowPulse() const { return enabled_ && glow_pulse_.enabled; }
bool hasHueCycle() const { return enabled_ && hue_cycle_.enabled; }
bool hasPositionalHue() const { return enabled_ && positional_hue_.enabled; }
bool hasEdgeTrace() const { return enabled_ && edge_trace_.enabled; } bool hasGradientBorder() const { return enabled_ && gradient_border_.enabled; } bool hasEmberRise() const { return enabled_ && ember_rise_.enabled; }
bool hasSpecularGlare() const { return enabled_ && !reduced_transparency_ && specular_glare_.enabled; }
bool hasSandstorm() const { return enabled_ && sandstorm_.enabled; }
bool hasViewportOverlay() const { return enabled_ && (viewport_overlay_.colorWashEnabled || viewport_overlay_.vignetteEnabled); }
/// True when any time-dependent effect is active and needs continuous redraws
bool hasActiveAnimation() const {
if (!enabled_) return false;
return hue_cycle_.enabled || rainbow_border_.enabled || shimmer_.enabled
|| glow_pulse_.enabled || edge_trace_.enabled || ember_rise_.enabled
|| gradient_border_.enabled || specular_glare_.enabled || sandstorm_.enabled
|| (viewport_overlay_.colorWashEnabled
&& (viewport_overlay_.washRotateSpeed > 0.0f || viewport_overlay_.washPulseSpeed > 0.0f));
}
private:
ThemeEffects() = default;
bool enabled_ = true;
bool reduced_transparency_ = false;
float time_ = 0.0f;
float bgOpacity_ = 1.0f; ///< window-opacity multiplier for effect alphas
// Viewport bounds (set each frame)
float vpMinY_ = 0.0f;
float vpMaxY_ = 1.0f;
// ---- Cached config from [effects] ----
struct HueCycleConfig {
bool enabled = false;
float speed = 0.1f, sat = 0.6f, val = 0.85f;
float range = 1.0f, offset = 0.0f;
} hue_cycle_;
struct RainbowBorderConfig {
bool enabled = false;
float speed = 0.05f, alpha = 0.25f;
std::vector<ImU32> stops;
} rainbow_border_;
struct ShimmerConfig {
bool enabled = false;
float speed = 0.12f, width = 80.0f, alpha = 0.06f, angle = 30.0f;
ImU32 color = IM_COL32(255,255,255,255);
} shimmer_;
struct PositionalHueConfig {
bool enabled = false;
ImVec4 topColor{1,0.42f,0.62f,1};
ImVec4 bottomColor{0.4f,0.91f,0.98f,1};
float strength = 0.3f;
} positional_hue_;
struct GlowPulseConfig {
bool enabled = false;
float speed = 2.0f, minAlpha = 0.0f, maxAlpha = 0.15f, radius = 4.0f;
ImU32 color = IM_COL32(255,255,255,255);
} glow_pulse_;
struct EdgeTraceConfig {
bool enabled = false;
float speed = 0.3f; ///< full circuits per second
float length = 0.20f; ///< fraction of perimeter lit (tail length)
float thickness = 1.5f; ///< line thickness in pixels
float alpha = 0.6f; ///< peak alpha at head of trace
ImU32 color = IM_COL32(255,255,255,255);
} edge_trace_;
struct EmberRiseConfig {
bool enabled = false;
int count = 8; ///< number of ember particles
float speed = 0.4f; ///< rise cycles per second
float particleSize = 1.5f;///< ember radius in pixels
float alpha = 0.5f; ///< peak alpha
ImU32 color = IM_COL32(255, 120, 20, 255);
} ember_rise_;
struct GradientBorderConfig {
bool enabled = false;
float speed = 0.15f; ///< full color shift cycles per second
float thickness = 1.5f; ///< border line thickness in pixels
float alpha = 0.6f; ///< peak alpha
ImU32 colorA = IM_COL32(206, 147, 216, 255); ///< first color (amethyst)
ImU32 colorB = IM_COL32(26, 35, 126, 255); ///< second color (indigo)
} gradient_border_;
struct SpecularGlareConfig {
bool enabled = false;
float speed = 0.02f; ///< drift speed (Lissajous orbit cycles/sec)
float intensity = 0.06f; ///< peak alpha at glare center
float radius = 0.5f; ///< glare radius as fraction of panel min-dim
int count = 2; ///< number of specular spots per panel
ImU32 color = IM_COL32(255, 255, 255, 255);
} specular_glare_;
struct SandstormConfig {
bool enabled = false;
int count = 80; ///< number of sand particles
float speed = 0.35f; ///< base horizontal velocity (viewport widths/sec)
float windAngle = 15.0f; ///< degrees from horizontal (+ve = downward)
float particleSize = 1.5f; ///< base particle radius in pixels
float alpha = 0.35f; ///< peak particle alpha
float gustSpeed = 0.07f; ///< gust oscillation frequency (Hz)
float gustStrength = 0.4f; ///< speed variation from gusts (fraction)
float streakLength = 3.0f; ///< motion blur multiplier for fast particles
ImU32 color = IM_COL32(200, 160, 96, 255);
} sandstorm_;
struct ViewportOverlayConfig {
// --- Color wash: full-screen 4-corner gradient overlay ---
bool colorWashEnabled = false;
ImU32 cornerTL = IM_COL32(255,100,0,255); ///< top-left corner color (RGB; alpha applied separately)
ImU32 cornerTR = IM_COL32(68,34,0,255); ///< top-right
ImU32 cornerBL = IM_COL32(68,17,0,255); ///< bottom-left
ImU32 cornerBR = IM_COL32(255,153,0,255); ///< bottom-right
float washAlpha = 0.05f; ///< overall wash intensity
float washRotateSpeed = 0.0f; ///< corner color rotation (turns/sec)
float washPulseSpeed = 0.0f; ///< alpha breathing speed (Hz)
float washPulseDepth = 0.0f; ///< pulse depth (0=none, 1=full fade)
// --- Vignette: edge darkening/tinting ---
bool vignetteEnabled = false;
ImU32 vignetteColor = IM_COL32(0,0,0,255); ///< tint color at edges
float vignetteRadius = 0.35f; ///< fade zone as fraction of viewport
float vignetteAlpha = 0.30f; ///< max alpha at outer edge
} viewport_overlay_;
/// Map a 0..1 fraction to a point on the rounded rect perimeter
ImVec2 perimeterPoint(ImVec2 pMin, ImVec2 pMax, float t, float rounding = 0.0f) const;
// Helper: parse hex color string "#RRGGBB" or "#RRGGBBAA" to ImU32
static ImU32 parseHexColor(const std::string& hex, ImU32 fallback = IM_COL32(255,255,255,255));
static ImVec4 parseHexColorVec4(const std::string& hex, ImVec4 fallback = ImVec4(1,1,1,1));
// Helper: sample a rotating multi-stop gradient
ImU32 sampleGradient(float t) const;
};
} // namespace effects
} // namespace ui
} // namespace dragonx