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)
346 lines
12 KiB
C++
346 lines
12 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include "colors.h"
|
|
#include "../effects/low_spec.h"
|
|
#include "../schema/ui_schema.h"
|
|
#include "imgui.h"
|
|
#include "imgui_internal.h"
|
|
#include <cmath>
|
|
|
|
namespace dragonx {
|
|
namespace ui {
|
|
namespace material {
|
|
|
|
// ============================================================================
|
|
// Material Design Elevation and Shadow System
|
|
// ============================================================================
|
|
// Based on https://m2.material.io/design/environment/elevation.html
|
|
//
|
|
// Material Design uses two light sources to create shadows:
|
|
// - Key light: Creates sharper, directional shadows
|
|
// - Ambient light: Creates softer, omnidirectional shadows
|
|
//
|
|
// In dark themes, elevation is primarily shown through surface color overlays
|
|
// rather than shadows. However, shadows can still enhance depth perception.
|
|
|
|
// ============================================================================
|
|
// Shadow Specifications
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @brief Individual shadow layer specification
|
|
*
|
|
* Material shadows are composed of multiple layers with different
|
|
* blur radii and offsets to simulate real-world lighting.
|
|
*/
|
|
struct ShadowLayer {
|
|
float offsetX; // Horizontal offset (typically 0)
|
|
float offsetY; // Vertical offset (key light from above)
|
|
float blurRadius; // Blur spread
|
|
float spreadRadius; // Size adjustment
|
|
float opacity; // Alpha 0.0-1.0
|
|
};
|
|
|
|
/**
|
|
* @brief Complete shadow specification for an elevation level
|
|
*/
|
|
struct ShadowSpec {
|
|
ShadowLayer umbra; // Darkest part, sharp edge
|
|
ShadowLayer penumbra; // Mid-tone, softer
|
|
ShadowLayer ambient; // Lightest, most diffuse
|
|
};
|
|
|
|
/**
|
|
* @brief Get shadow specification for elevation level
|
|
*
|
|
* @param elevationDp Elevation in dp (0, 1, 2, 3, 4, 6, 8, 12, 16, 24)
|
|
* @return ShadowSpec for the elevation
|
|
*/
|
|
ShadowSpec GetShadowSpec(int elevationDp);
|
|
|
|
// ============================================================================
|
|
// Shadow Rendering
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @brief Draw Material Design shadow for a rectangle
|
|
*
|
|
* Uses multi-layer soft shadow rendering to approximate Material shadows.
|
|
*
|
|
* @param drawList ImGui draw list
|
|
* @param rect Rectangle bounds
|
|
* @param elevationDp Elevation in dp
|
|
* @param cornerRadius Corner radius for rounded rectangles
|
|
*/
|
|
void DrawShadow(ImDrawList* drawList, const ImRect& rect, int elevationDp, float cornerRadius = 0);
|
|
|
|
/**
|
|
* @brief Draw shadow with position/size parameters
|
|
*/
|
|
void DrawShadow(ImDrawList* drawList, const ImVec2& pos, const ImVec2& size,
|
|
int elevationDp, float cornerRadius = 0);
|
|
|
|
/**
|
|
* @brief Draw soft shadow (single layer, for custom effects)
|
|
*
|
|
* @param drawList ImGui draw list
|
|
* @param rect Rectangle bounds
|
|
* @param color Shadow color with alpha
|
|
* @param blurRadius Blur amount
|
|
* @param offset Shadow offset
|
|
* @param cornerRadius Corner radius
|
|
*/
|
|
void DrawSoftShadow(ImDrawList* drawList, const ImRect& rect, ImU32 color,
|
|
float blurRadius, const ImVec2& offset = ImVec2(0, 0),
|
|
float cornerRadius = 0);
|
|
|
|
// ============================================================================
|
|
// Elevation Transition Helper
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @brief Animated elevation value
|
|
*
|
|
* Use this to smoothly transition between elevation levels (e.g., card hover)
|
|
*/
|
|
class ElevationAnimator {
|
|
public:
|
|
ElevationAnimator(int initialElevation = 0);
|
|
|
|
/**
|
|
* @brief Set target elevation (will animate towards it)
|
|
*/
|
|
void setTarget(int targetElevation);
|
|
|
|
/**
|
|
* @brief Update animation (call each frame)
|
|
* @param deltaTime Frame delta time
|
|
*/
|
|
void update(float deltaTime);
|
|
|
|
/**
|
|
* @brief Get current animated elevation value
|
|
*/
|
|
float getCurrent() const { return m_current; }
|
|
|
|
/**
|
|
* @brief Get current elevation as integer (for shadow lookup)
|
|
*/
|
|
int getCurrentInt() const { return static_cast<int>(m_current + 0.5f); }
|
|
|
|
/**
|
|
* @brief Check if currently animating
|
|
*/
|
|
bool isAnimating() const { return m_current != m_target; }
|
|
|
|
private:
|
|
float m_current;
|
|
float m_target;
|
|
float m_animationSpeed = 16.0f; // dp per second
|
|
};
|
|
|
|
// ============================================================================
|
|
// Implementation
|
|
// ============================================================================
|
|
|
|
inline ShadowSpec GetShadowSpec(int elevationDp) {
|
|
// Material Design shadow values adapted from the spec
|
|
// These approximate the CSS box-shadow values from material.io
|
|
|
|
switch (elevationDp) {
|
|
case 0:
|
|
return {
|
|
{0, 0, 0, 0, 0}, // No shadow
|
|
{0, 0, 0, 0, 0},
|
|
{0, 0, 0, 0, 0}
|
|
};
|
|
case 1:
|
|
return {
|
|
{0, 2, 1, -1, 0.2f}, // Umbra
|
|
{0, 1, 1, 0, 0.14f}, // Penumbra
|
|
{0, 1, 3, 0, 0.12f} // Ambient
|
|
};
|
|
case 2:
|
|
return {
|
|
{0, 3, 1, -2, 0.2f},
|
|
{0, 2, 2, 0, 0.14f},
|
|
{0, 1, 5, 0, 0.12f}
|
|
};
|
|
case 3:
|
|
return {
|
|
{0, 3, 3, -2, 0.2f},
|
|
{0, 3, 4, 0, 0.14f},
|
|
{0, 1, 8, 0, 0.12f}
|
|
};
|
|
case 4:
|
|
return {
|
|
{0, 2, 4, -1, 0.2f},
|
|
{0, 4, 5, 0, 0.14f},
|
|
{0, 1, 10, 0, 0.12f}
|
|
};
|
|
case 6:
|
|
return {
|
|
{0, 3, 5, -1, 0.2f},
|
|
{0, 6, 10, 0, 0.14f},
|
|
{0, 1, 18, 0, 0.12f}
|
|
};
|
|
case 8:
|
|
return {
|
|
{0, 5, 5, -3, 0.2f},
|
|
{0, 8, 10, 1, 0.14f},
|
|
{0, 3, 14, 2, 0.12f}
|
|
};
|
|
case 12:
|
|
return {
|
|
{0, 7, 8, -4, 0.2f},
|
|
{0, 12, 17, 2, 0.14f},
|
|
{0, 5, 22, 4, 0.12f}
|
|
};
|
|
case 16:
|
|
return {
|
|
{0, 8, 10, -5, 0.2f},
|
|
{0, 16, 24, 2, 0.14f},
|
|
{0, 6, 30, 5, 0.12f}
|
|
};
|
|
case 24:
|
|
return {
|
|
{0, 11, 15, -7, 0.2f},
|
|
{0, 24, 38, 3, 0.14f},
|
|
{0, 9, 46, 8, 0.12f}
|
|
};
|
|
default:
|
|
// Interpolate for non-standard elevations
|
|
if (elevationDp < 0) return GetShadowSpec(0);
|
|
if (elevationDp > 24) return GetShadowSpec(24);
|
|
|
|
// Find nearest standard elevation
|
|
int lower = 0, upper = 1;
|
|
int standards[] = {0, 1, 2, 3, 4, 6, 8, 12, 16, 24};
|
|
for (int i = 0; i < 9; i++) {
|
|
if (standards[i] <= elevationDp && standards[i + 1] >= elevationDp) {
|
|
lower = standards[i];
|
|
upper = standards[i + 1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Use nearest
|
|
return GetShadowSpec((elevationDp - lower < upper - elevationDp) ? lower : upper);
|
|
}
|
|
}
|
|
|
|
inline void DrawSoftShadow(ImDrawList* drawList, const ImRect& rect, ImU32 color,
|
|
float blurRadius, const ImVec2& offset, float cornerRadius) {
|
|
if (blurRadius <= 0 || (color & IM_COL32_A_MASK) == 0)
|
|
return;
|
|
|
|
// For ImGui, we'll simulate soft shadows using multiple semi-transparent layers
|
|
// This is a performance-friendly approximation
|
|
|
|
// In low-spec mode use only 1 layer instead of up to 8
|
|
const int numLayers = dragonx::ui::effects::isLowSpecMode()
|
|
? 1
|
|
: ImClamp((int)(blurRadius / 2), 2, 8);
|
|
const float layerStep = blurRadius / numLayers;
|
|
|
|
// Extract base alpha
|
|
float baseAlpha = ((color >> IM_COL32_A_SHIFT) & 0xFF) / 255.0f;
|
|
ImU32 baseColor = color & ~IM_COL32_A_MASK;
|
|
|
|
for (int i = numLayers - 1; i >= 0; i--) {
|
|
float expansion = layerStep * (i + 1);
|
|
float alpha = baseAlpha * (1.0f - (float)i / numLayers) / numLayers;
|
|
|
|
ImU32 layerColor = baseColor | (((ImU32)(alpha * 255)) << IM_COL32_A_SHIFT);
|
|
|
|
ImRect expandedRect(
|
|
rect.Min.x - expansion + offset.x,
|
|
rect.Min.y - expansion + offset.y,
|
|
rect.Max.x + expansion + offset.x,
|
|
rect.Max.y + expansion + offset.y
|
|
);
|
|
|
|
drawList->AddRectFilled(expandedRect.Min, expandedRect.Max, layerColor,
|
|
cornerRadius + expansion * 0.5f);
|
|
}
|
|
}
|
|
|
|
inline void DrawShadow(ImDrawList* drawList, const ImRect& rect, int elevationDp, float cornerRadius) {
|
|
if (elevationDp <= 0)
|
|
return;
|
|
|
|
ShadowSpec spec = GetShadowSpec(elevationDp);
|
|
|
|
// Shadow multiplier: light themes need stronger shadows for card depth,
|
|
// dark themes rely more on surface color overlay for elevation.
|
|
// Configurable via ui.toml [style] shadow-multiplier / shadow-multiplier-light.
|
|
const float shadowMultiplier = schema::UI().isDarkTheme()
|
|
? schema::UI().drawElement("style", "shadow-multiplier").sizeOr(0.6f)
|
|
: schema::UI().drawElement("style", "shadow-multiplier-light").sizeOr(1.0f);
|
|
|
|
// Draw ambient shadow (largest, most diffuse)
|
|
if (spec.ambient.opacity > 0) {
|
|
ImU32 ambientColor = IM_COL32(0, 0, 0, (int)(spec.ambient.opacity * shadowMultiplier * 255));
|
|
ImRect ambientRect = rect;
|
|
ambientRect.Expand(spec.ambient.spreadRadius);
|
|
DrawSoftShadow(drawList, ambientRect, ambientColor, spec.ambient.blurRadius,
|
|
ImVec2(spec.ambient.offsetX, spec.ambient.offsetY), cornerRadius);
|
|
}
|
|
|
|
// Draw penumbra (medium)
|
|
if (spec.penumbra.opacity > 0) {
|
|
ImU32 penumbraColor = IM_COL32(0, 0, 0, (int)(spec.penumbra.opacity * shadowMultiplier * 255));
|
|
ImRect penumbraRect = rect;
|
|
penumbraRect.Expand(spec.penumbra.spreadRadius);
|
|
DrawSoftShadow(drawList, penumbraRect, penumbraColor, spec.penumbra.blurRadius,
|
|
ImVec2(spec.penumbra.offsetX, spec.penumbra.offsetY), cornerRadius);
|
|
}
|
|
|
|
// Draw umbra (sharpest, darkest)
|
|
if (spec.umbra.opacity > 0) {
|
|
ImU32 umbraColor = IM_COL32(0, 0, 0, (int)(spec.umbra.opacity * shadowMultiplier * 255));
|
|
ImRect umbraRect = rect;
|
|
umbraRect.Expand(spec.umbra.spreadRadius);
|
|
DrawSoftShadow(drawList, umbraRect, umbraColor, spec.umbra.blurRadius,
|
|
ImVec2(spec.umbra.offsetX, spec.umbra.offsetY), cornerRadius);
|
|
}
|
|
}
|
|
|
|
inline void DrawShadow(ImDrawList* drawList, const ImVec2& pos, const ImVec2& size,
|
|
int elevationDp, float cornerRadius) {
|
|
ImRect rect(pos, ImVec2(pos.x + size.x, pos.y + size.y));
|
|
DrawShadow(drawList, rect, elevationDp, cornerRadius);
|
|
}
|
|
|
|
inline ElevationAnimator::ElevationAnimator(int initialElevation)
|
|
: m_current(static_cast<float>(initialElevation))
|
|
, m_target(static_cast<float>(initialElevation))
|
|
{
|
|
}
|
|
|
|
inline void ElevationAnimator::setTarget(int targetElevation) {
|
|
m_target = static_cast<float>(targetElevation);
|
|
}
|
|
|
|
inline void ElevationAnimator::update(float deltaTime) {
|
|
if (m_current == m_target)
|
|
return;
|
|
|
|
float diff = m_target - m_current;
|
|
float change = m_animationSpeed * deltaTime;
|
|
|
|
if (std::abs(diff) <= change) {
|
|
m_current = m_target;
|
|
} else {
|
|
m_current += (diff > 0 ? 1 : -1) * change;
|
|
}
|
|
}
|
|
|
|
} // namespace material
|
|
} // namespace ui
|
|
} // namespace dragonx
|