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:
1695
src/ui/effects/acrylic.cpp
Normal file
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
456
src/ui/effects/acrylic.h
Normal 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.3–1.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.3–1.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
|
||||
282
src/ui/effects/blur_shader.cpp
Normal file
282
src/ui/effects/blur_shader.cpp
Normal 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
|
||||
211
src/ui/effects/blur_shader.h
Normal file
211
src/ui/effects/blur_shader.h
Normal 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
|
||||
302
src/ui/effects/framebuffer.cpp
Normal file
302
src/ui/effects/framebuffer.cpp
Normal 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
|
||||
193
src/ui/effects/framebuffer.h
Normal file
193
src/ui/effects/framebuffer.h
Normal 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
|
||||
1178
src/ui/effects/imgui_acrylic.cpp
Normal file
1178
src/ui/effects/imgui_acrylic.cpp
Normal file
File diff suppressed because it is too large
Load Diff
381
src/ui/effects/imgui_acrylic.h
Normal file
381
src/ui/effects/imgui_acrylic.h
Normal 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.3–1.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 0–5 (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
|
||||
24
src/ui/effects/low_spec.cpp
Normal file
24
src/ui/effects/low_spec.cpp
Normal 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
21
src/ui/effects/low_spec.h
Normal 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
|
||||
354
src/ui/effects/noise_texture.cpp
Normal file
354
src/ui/effects/noise_texture.cpp
Normal 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
|
||||
113
src/ui/effects/noise_texture.h
Normal file
113
src/ui/effects/noise_texture.h
Normal 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
|
||||
418
src/ui/effects/scroll_fade_fbo.h
Normal file
418
src/ui/effects/scroll_fade_fbo.h
Normal file
@@ -0,0 +1,418 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
//
|
||||
// Offscreen render-target scroll fade — the ImGui equivalent of CSS mask-image.
|
||||
// Renders scrollable content to an offscreen surface, then composites it back
|
||||
// as a textured mesh strip with vertex alpha for edge fading.
|
||||
// This produces a true per-pixel fade that works with any background
|
||||
// (including acrylic/backdrop transparency).
|
||||
//
|
||||
// Supports both OpenGL (DRAGONX_HAS_GLAD) and DX11 (DRAGONX_USE_DX11).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
#include <cstdio>
|
||||
|
||||
// ============================================================================
|
||||
// Platform detection
|
||||
// ============================================================================
|
||||
#if defined(DRAGONX_USE_DX11)
|
||||
#include <d3d11.h>
|
||||
#define SCROLL_FADE_HAS_OFFSCREEN 1
|
||||
#define SCROLL_FADE_DX11 1
|
||||
#elif defined(DRAGONX_HAS_GLAD)
|
||||
#include <glad/gl.h>
|
||||
#include "../../util/logger.h"
|
||||
#ifndef GL_FRAMEBUFFER_BINDING
|
||||
#define GL_FRAMEBUFFER_BINDING 0x8CA6
|
||||
#endif
|
||||
#ifndef GL_VIEWPORT
|
||||
#define GL_VIEWPORT 0x0BA2
|
||||
#endif
|
||||
#ifndef GL_SCISSOR_TEST
|
||||
#define GL_SCISSOR_TEST 0x0C11
|
||||
#endif
|
||||
#define SCROLL_FADE_HAS_OFFSCREEN 1
|
||||
#define SCROLL_FADE_GL 1
|
||||
#endif
|
||||
|
||||
#ifdef SCROLL_FADE_HAS_OFFSCREEN
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
namespace effects {
|
||||
|
||||
// ============================================================================
|
||||
// ScrollFadeRT — manages an offscreen render target for scroll-fade rendering
|
||||
// ============================================================================
|
||||
|
||||
class ScrollFadeRT {
|
||||
public:
|
||||
ScrollFadeRT() = default;
|
||||
~ScrollFadeRT() { destroy(); }
|
||||
|
||||
// Non-copyable
|
||||
ScrollFadeRT(const ScrollFadeRT&) = delete;
|
||||
ScrollFadeRT& operator=(const ScrollFadeRT&) = delete;
|
||||
|
||||
/// Ensure RT matches the required dimensions. Returns true if ready.
|
||||
bool ensure(int w, int h) {
|
||||
if (w <= 0 || h <= 0) return false;
|
||||
if (isValid() && w == width_ && h == height_) return true;
|
||||
return init(w, h);
|
||||
}
|
||||
|
||||
void destroy();
|
||||
bool isValid() const;
|
||||
|
||||
/// Get the texture as an ImTextureID for compositing.
|
||||
ImTextureID textureID() const;
|
||||
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
|
||||
#ifdef SCROLL_FADE_DX11
|
||||
ID3D11RenderTargetView* rtv() const { return rtv_; }
|
||||
#endif
|
||||
#ifdef SCROLL_FADE_GL
|
||||
unsigned int fbo() const { return fbo_; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool init(int w, int h);
|
||||
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
|
||||
#ifdef SCROLL_FADE_DX11
|
||||
ID3D11Texture2D* tex_ = nullptr;
|
||||
ID3D11RenderTargetView* rtv_ = nullptr;
|
||||
ID3D11ShaderResourceView* srv_ = nullptr;
|
||||
#endif
|
||||
#ifdef SCROLL_FADE_GL
|
||||
unsigned int fbo_ = 0;
|
||||
unsigned int colorTex_ = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Implementations
|
||||
// ============================================================================
|
||||
|
||||
#ifdef SCROLL_FADE_DX11
|
||||
|
||||
// --- DX11 helpers to get device/context from ImGui backend ---
|
||||
inline ID3D11Device* GetDX11Device() {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (!io.BackendRendererUserData) return nullptr;
|
||||
return *reinterpret_cast<ID3D11Device**>(io.BackendRendererUserData);
|
||||
}
|
||||
inline ID3D11DeviceContext* GetDX11Context() {
|
||||
ID3D11Device* dev = GetDX11Device();
|
||||
if (!dev) return nullptr;
|
||||
ID3D11DeviceContext* ctx = nullptr;
|
||||
dev->GetImmediateContext(&ctx);
|
||||
return ctx; // caller must Release()
|
||||
}
|
||||
|
||||
inline bool ScrollFadeRT::init(int w, int h) {
|
||||
destroy();
|
||||
ID3D11Device* dev = GetDX11Device();
|
||||
if (!dev) return false;
|
||||
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
// Create texture
|
||||
D3D11_TEXTURE2D_DESC td = {};
|
||||
td.Width = (UINT)w;
|
||||
td.Height = (UINT)h;
|
||||
td.MipLevels = 1;
|
||||
td.ArraySize = 1;
|
||||
td.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
td.SampleDesc.Count = 1;
|
||||
td.Usage = D3D11_USAGE_DEFAULT;
|
||||
td.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
|
||||
|
||||
if (FAILED(dev->CreateTexture2D(&td, nullptr, &tex_))) {
|
||||
DEBUG_LOGF("ScrollFadeRT: CreateTexture2D failed\n");
|
||||
destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Render target view
|
||||
if (FAILED(dev->CreateRenderTargetView(tex_, nullptr, &rtv_))) {
|
||||
DEBUG_LOGF("ScrollFadeRT: CreateRenderTargetView failed\n");
|
||||
destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Shader resource view (for sampling as texture)
|
||||
if (FAILED(dev->CreateShaderResourceView(tex_, nullptr, &srv_))) {
|
||||
DEBUG_LOGF("ScrollFadeRT: CreateShaderResourceView failed\n");
|
||||
destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void ScrollFadeRT::destroy() {
|
||||
if (srv_) { srv_->Release(); srv_ = nullptr; }
|
||||
if (rtv_) { rtv_->Release(); rtv_ = nullptr; }
|
||||
if (tex_) { tex_->Release(); tex_ = nullptr; }
|
||||
width_ = height_ = 0;
|
||||
}
|
||||
|
||||
inline bool ScrollFadeRT::isValid() const { return rtv_ != nullptr; }
|
||||
|
||||
inline ImTextureID ScrollFadeRT::textureID() const {
|
||||
return (ImTextureID)srv_;
|
||||
}
|
||||
|
||||
#endif // SCROLL_FADE_DX11
|
||||
|
||||
#ifdef SCROLL_FADE_GL
|
||||
|
||||
inline bool ScrollFadeRT::init(int w, int h) {
|
||||
destroy();
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
glGenFramebuffers(1, &fbo_);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
||||
|
||||
glGenTextures(1, &colorTex_);
|
||||
glBindTexture(GL_TEXTURE_2D, colorTex_);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D, colorTex_, 0);
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
DEBUG_LOGF("ScrollFadeRT: FBO incomplete (0x%X)\n", status);
|
||||
destroy();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void ScrollFadeRT::destroy() {
|
||||
if (colorTex_) { glDeleteTextures(1, &colorTex_); colorTex_ = 0; }
|
||||
if (fbo_) { glDeleteFramebuffers(1, &fbo_); fbo_ = 0; }
|
||||
width_ = height_ = 0;
|
||||
}
|
||||
|
||||
inline bool ScrollFadeRT::isValid() const { return fbo_ != 0; }
|
||||
|
||||
inline ImTextureID ScrollFadeRT::textureID() const {
|
||||
return (ImTextureID)(intptr_t)colorTex_;
|
||||
}
|
||||
|
||||
#endif // SCROLL_FADE_GL
|
||||
|
||||
// ============================================================================
|
||||
// Callback state — singleton storage for bind/unbind data
|
||||
// ============================================================================
|
||||
|
||||
struct ScrollFadeState {
|
||||
#ifdef SCROLL_FADE_DX11
|
||||
ID3D11RenderTargetView* offscreenRTV = nullptr;
|
||||
ID3D11RenderTargetView* savedRTV = nullptr;
|
||||
ID3D11DepthStencilView* savedDSV = nullptr;
|
||||
D3D11_VIEWPORT savedVP = {};
|
||||
#endif
|
||||
#ifdef SCROLL_FADE_GL
|
||||
unsigned int fbo = 0;
|
||||
int savedFBO = 0;
|
||||
int savedVP[4] = {};
|
||||
bool savedScissorEnabled = true; // ImGui always has scissor enabled
|
||||
#endif
|
||||
int vpW = 0, vpH = 0; // framebuffer pixel dimensions for viewport
|
||||
};
|
||||
|
||||
inline ScrollFadeState& GetScrollFadeState() {
|
||||
static ScrollFadeState s;
|
||||
return s;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Callbacks — inserted into the draw list via AddCallback
|
||||
// ============================================================================
|
||||
|
||||
#ifdef SCROLL_FADE_DX11
|
||||
|
||||
inline void BindRTCallback(const ImDrawList*, const ImDrawCmd*) {
|
||||
auto& st = GetScrollFadeState();
|
||||
ID3D11DeviceContext* ctx = GetDX11Context();
|
||||
if (!ctx) return;
|
||||
|
||||
// Save current RT and viewport
|
||||
UINT numVP = 1;
|
||||
ctx->OMGetRenderTargets(1, &st.savedRTV, &st.savedDSV);
|
||||
ctx->RSGetViewports(&numVP, &st.savedVP);
|
||||
|
||||
// Bind offscreen RT
|
||||
ctx->OMSetRenderTargets(1, &st.offscreenRTV, nullptr);
|
||||
|
||||
// Set viewport to match RT size
|
||||
D3D11_VIEWPORT vp = {};
|
||||
vp.Width = (FLOAT)st.vpW;
|
||||
vp.Height = (FLOAT)st.vpH;
|
||||
vp.MaxDepth = 1.0f;
|
||||
ctx->RSSetViewports(1, &vp);
|
||||
|
||||
// Clear to transparent
|
||||
float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
ctx->ClearRenderTargetView(st.offscreenRTV, clearColor);
|
||||
|
||||
ctx->Release();
|
||||
}
|
||||
|
||||
inline void UnbindRTCallback(const ImDrawList*, const ImDrawCmd*) {
|
||||
auto& st = GetScrollFadeState();
|
||||
ID3D11DeviceContext* ctx = GetDX11Context();
|
||||
if (!ctx) return;
|
||||
|
||||
// Restore previous RT and viewport
|
||||
ctx->OMSetRenderTargets(1, &st.savedRTV, st.savedDSV);
|
||||
ctx->RSSetViewports(1, &st.savedVP);
|
||||
|
||||
// Release the refs from OMGetRenderTargets
|
||||
if (st.savedRTV) { st.savedRTV->Release(); st.savedRTV = nullptr; }
|
||||
if (st.savedDSV) { st.savedDSV->Release(); st.savedDSV = nullptr; }
|
||||
|
||||
ctx->Release();
|
||||
}
|
||||
|
||||
#endif // SCROLL_FADE_DX11
|
||||
|
||||
#ifdef SCROLL_FADE_GL
|
||||
|
||||
inline void BindRTCallback(const ImDrawList*, const ImDrawCmd*) {
|
||||
auto& st = GetScrollFadeState();
|
||||
|
||||
// Save current FBO and viewport
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &st.savedFBO);
|
||||
glGetIntegerv(GL_VIEWPORT, st.savedVP);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, st.fbo);
|
||||
glViewport(0, 0, st.vpW, st.vpH);
|
||||
|
||||
// Disable scissor test inside the FBO. ImGui's renderer computes
|
||||
// scissor rects relative to the main framebuffer dimensions — those
|
||||
// coordinates would be wrong for our offscreen surface. The child
|
||||
// window's content is already bounded by ImGui's layout, and the
|
||||
// composite step applies its own clip rect, so skipping scissor
|
||||
// in the FBO is safe.
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
inline void UnbindRTCallback(const ImDrawList*, const ImDrawCmd*) {
|
||||
auto& st = GetScrollFadeState();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, (unsigned int)st.savedFBO);
|
||||
glViewport(st.savedVP[0], st.savedVP[1], st.savedVP[2], st.savedVP[3]);
|
||||
if (st.savedScissorEnabled)
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
#endif // SCROLL_FADE_GL
|
||||
|
||||
// ============================================================================
|
||||
// Composite helper — draw the RT texture as a mesh strip with alpha fade
|
||||
// ============================================================================
|
||||
|
||||
/// Draw the offscreen texture onto `dl` as a vertical strip with alpha=0 at
|
||||
/// the faded edges and alpha=1 in the middle. Produces a true CSS-like
|
||||
/// mask-image: linear-gradient() result.
|
||||
///
|
||||
/// @param logicalW, logicalH Logical display dimensions (DisplaySize) for
|
||||
/// UV calculation — NOT the RT pixel dimensions. ImGui screen coords
|
||||
/// are in logical units, and the FBO projection maps them 1:1 to the
|
||||
/// logical coordinate space, so UVs must divide by logical size.
|
||||
inline void CompositeWithFade(ImDrawList* dl,
|
||||
ImTextureID texID,
|
||||
const ImVec2& screenMin,
|
||||
const ImVec2& screenMax,
|
||||
int logicalW, int logicalH,
|
||||
float fadeTop, float fadeBot,
|
||||
bool needTop, bool needBot)
|
||||
{
|
||||
float left = screenMin.x;
|
||||
float right = screenMax.x;
|
||||
float y0 = screenMin.y;
|
||||
float y1 = screenMin.y + (needTop ? fadeTop : 0.0f);
|
||||
float y2 = screenMax.y - (needBot ? fadeBot : 0.0f);
|
||||
float y3 = screenMax.y;
|
||||
|
||||
// Clamp in case fade zones overlap
|
||||
if (y1 > y2) { float mid = (y0 + y3) * 0.5f; y1 = y2 = mid; }
|
||||
|
||||
// UV coordinates — map screen position (logical) to render target texture.
|
||||
// Screen coords are in logical (DisplaySize) space. The FBO projection
|
||||
// maps these 1:1, so divide by logical dimensions to get [0,1] UVs.
|
||||
float uL = screenMin.x / (float)logicalW;
|
||||
float uR = screenMax.x / (float)logicalW;
|
||||
|
||||
#ifdef SCROLL_FADE_GL
|
||||
// OpenGL: FBO Y is flipped (ImGui top=0 → GL bottom=0)
|
||||
auto uvY = [&](float y) -> float { return 1.0f - y / (float)logicalH; };
|
||||
#else
|
||||
// DX11: no Y flip (both ImGui and DX11 have (0,0) at top-left)
|
||||
auto uvY = [&](float y) -> float { return y / (float)logicalH; };
|
||||
#endif
|
||||
|
||||
ImU32 colOpaque = IM_COL32(255, 255, 255, 255);
|
||||
ImU32 colClear = IM_COL32(255, 255, 255, 0);
|
||||
ImU32 colTop = needTop ? colClear : colOpaque;
|
||||
ImU32 colBot = needBot ? colClear : colOpaque;
|
||||
|
||||
dl->PushTextureID(texID);
|
||||
dl->PrimReserve(18, 8);
|
||||
|
||||
ImDrawVert* vtx = dl->_VtxWritePtr;
|
||||
ImDrawIdx* idx = dl->_IdxWritePtr;
|
||||
ImDrawIdx base = (ImDrawIdx)dl->_VtxCurrentIdx;
|
||||
|
||||
vtx[0] = { ImVec2(left, y0), ImVec2(uL, uvY(y0)), colTop };
|
||||
vtx[1] = { ImVec2(right, y0), ImVec2(uR, uvY(y0)), colTop };
|
||||
vtx[2] = { ImVec2(left, y1), ImVec2(uL, uvY(y1)), colOpaque };
|
||||
vtx[3] = { ImVec2(right, y1), ImVec2(uR, uvY(y1)), colOpaque };
|
||||
vtx[4] = { ImVec2(left, y2), ImVec2(uL, uvY(y2)), colOpaque };
|
||||
vtx[5] = { ImVec2(right, y2), ImVec2(uR, uvY(y2)), colOpaque };
|
||||
vtx[6] = { ImVec2(left, y3), ImVec2(uL, uvY(y3)), colBot };
|
||||
vtx[7] = { ImVec2(right, y3), ImVec2(uR, uvY(y3)), colBot };
|
||||
|
||||
idx[0] = base+0; idx[1] = base+1; idx[2] = base+3;
|
||||
idx[3] = base+0; idx[4] = base+3; idx[5] = base+2;
|
||||
idx[6] = base+2; idx[7] = base+3; idx[8] = base+5;
|
||||
idx[9] = base+2; idx[10] = base+5; idx[11] = base+4;
|
||||
idx[12] = base+4; idx[13] = base+5; idx[14] = base+7;
|
||||
idx[15] = base+4; idx[16] = base+7; idx[17] = base+6;
|
||||
|
||||
dl->_VtxWritePtr += 8;
|
||||
dl->_IdxWritePtr += 18;
|
||||
dl->_VtxCurrentIdx += 8;
|
||||
|
||||
dl->PopTextureID();
|
||||
}
|
||||
|
||||
} // namespace effects
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
|
||||
#endif // SCROLL_FADE_HAS_OFFSCREEN
|
||||
416
src/ui/effects/scroll_fade_shader.h
Normal file
416
src/ui/effects/scroll_fade_shader.h
Normal 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
|
||||
1152
src/ui/effects/theme_effects.cpp
Normal file
1152
src/ui/effects/theme_effects.cpp
Normal file
File diff suppressed because it is too large
Load Diff
244
src/ui/effects/theme_effects.h
Normal file
244
src/ui/effects/theme_effects.h
Normal 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
|
||||
Reference in New Issue
Block a user