// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #pragma once #include #include #include 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 per-panel visual effect is active (rainbow, shimmer, specular, edge, ember, gradient) bool hasAnyPanelEffect() const { return hasRainbowBorder() || hasShimmer() || hasSpecularGlare() || hasEdgeTrace() || hasEmberRise() || hasGradientBorder(); } /// 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 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