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)
453 lines
12 KiB
C++
453 lines
12 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include "imgui.h"
|
|
#include <cmath>
|
|
#include <functional>
|
|
|
|
namespace dragonx {
|
|
namespace ui {
|
|
namespace material {
|
|
|
|
// ============================================================================
|
|
// Material Design Motion System
|
|
// ============================================================================
|
|
// Based on https://m2.material.io/design/motion/speed.html
|
|
// and https://m2.material.io/design/motion/customization.html
|
|
//
|
|
// Material motion uses specific easing curves and durations to create
|
|
// natural, responsive animations that feel connected to user input.
|
|
|
|
// ============================================================================
|
|
// Standard Durations (in seconds)
|
|
// ============================================================================
|
|
|
|
namespace duration {
|
|
// Simple transitions (toggle, fade)
|
|
constexpr float Instant = 0.0f;
|
|
constexpr float VeryFast = 0.05f; // 50ms
|
|
constexpr float Fast = 0.1f; // 100ms - simple toggles
|
|
constexpr float Short = 0.15f; // 150ms
|
|
|
|
// Standard transitions
|
|
constexpr float Medium = 0.2f; // 200ms - collapse, simple move
|
|
constexpr float Standard = 0.25f; // 250ms - expand, standard
|
|
constexpr float Long = 0.3f; // 300ms - large transforms
|
|
|
|
// Complex transitions
|
|
constexpr float Complex = 0.375f; // 375ms
|
|
constexpr float VeryLong = 0.5f; // 500ms - elaborate sequences
|
|
|
|
// Screen transitions
|
|
constexpr float EnterScreen = 0.225f; // Entering screen
|
|
constexpr float ExitScreen = 0.195f; // Leaving screen
|
|
constexpr float ScreenChange = 0.3f; // Full screen transition
|
|
}
|
|
|
|
// ============================================================================
|
|
// Easing Curves
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @brief Cubic bezier curve evaluation
|
|
*
|
|
* Evaluates a cubic bezier curve defined by control points (0,0), (x1,y1), (x2,y2), (1,1)
|
|
*
|
|
* @param t Progress 0.0-1.0
|
|
* @param x1 First control point X
|
|
* @param y1 First control point Y
|
|
* @param x2 Second control point X
|
|
* @param y2 Second control point Y
|
|
* @return Eased value
|
|
*/
|
|
float CubicBezier(float t, float x1, float y1, float x2, float y2);
|
|
|
|
/**
|
|
* @brief Standard easing - for objects moving between on-screen positions
|
|
*
|
|
* CSS: cubic-bezier(0.4, 0.0, 0.2, 1.0)
|
|
* Starts quickly, slows down to rest
|
|
*/
|
|
float EaseStandard(float t);
|
|
|
|
/**
|
|
* @brief Deceleration easing - for objects entering the screen
|
|
*
|
|
* CSS: cubic-bezier(0.0, 0.0, 0.2, 1.0)
|
|
* Starts at full velocity, decelerates to rest
|
|
*/
|
|
float EaseDecelerate(float t);
|
|
|
|
/**
|
|
* @brief Acceleration easing - for objects leaving the screen
|
|
*
|
|
* CSS: cubic-bezier(0.4, 0.0, 1.0, 1.0)
|
|
* Accelerates from rest, exits at full speed
|
|
*/
|
|
float EaseAccelerate(float t);
|
|
|
|
/**
|
|
* @brief Sharp easing - for objects that may return to screen
|
|
*
|
|
* CSS: cubic-bezier(0.4, 0.0, 0.6, 1.0)
|
|
* Quicker than standard, maintains connection
|
|
*/
|
|
float EaseSharp(float t);
|
|
|
|
/**
|
|
* @brief Linear interpolation (no easing)
|
|
*/
|
|
float EaseLinear(float t);
|
|
|
|
/**
|
|
* @brief Overshoot easing - goes past target then settles
|
|
*
|
|
* Good for bouncy, playful animations
|
|
*/
|
|
float EaseOvershoot(float t, float overshoot = 1.70158f);
|
|
|
|
/**
|
|
* @brief Elastic easing - springy oscillation
|
|
*/
|
|
float EaseElastic(float t);
|
|
|
|
// ============================================================================
|
|
// Easing Function Type
|
|
// ============================================================================
|
|
|
|
using EasingFunction = float(*)(float);
|
|
|
|
// ============================================================================
|
|
// Animated Value
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @brief Animated value with automatic interpolation
|
|
*
|
|
* Template class for smooth value transitions.
|
|
*/
|
|
template<typename T>
|
|
class AnimatedValue {
|
|
public:
|
|
AnimatedValue(const T& initialValue = T())
|
|
: m_current(initialValue)
|
|
, m_target(initialValue)
|
|
, m_start(initialValue)
|
|
, m_duration(duration::Standard)
|
|
, m_elapsed(0)
|
|
, m_easingFunc(EaseStandard)
|
|
, m_animating(false)
|
|
{}
|
|
|
|
/**
|
|
* @brief Set target value with animation
|
|
*/
|
|
void animateTo(const T& target, float dur = duration::Standard,
|
|
EasingFunction easing = EaseStandard) {
|
|
if (target == m_target && m_animating)
|
|
return; // Already animating to this target
|
|
|
|
m_start = m_current;
|
|
m_target = target;
|
|
m_duration = dur;
|
|
m_elapsed = 0;
|
|
m_easingFunc = easing;
|
|
m_animating = true;
|
|
}
|
|
|
|
/**
|
|
* @brief Set value immediately (no animation)
|
|
*/
|
|
void set(const T& value) {
|
|
m_current = value;
|
|
m_target = value;
|
|
m_start = value;
|
|
m_animating = false;
|
|
}
|
|
|
|
/**
|
|
* @brief Update animation (call each frame)
|
|
* @param deltaTime Frame delta time in seconds
|
|
*/
|
|
void update(float deltaTime) {
|
|
if (!m_animating)
|
|
return;
|
|
|
|
m_elapsed += deltaTime;
|
|
|
|
if (m_elapsed >= m_duration) {
|
|
m_current = m_target;
|
|
m_animating = false;
|
|
} else {
|
|
float t = m_elapsed / m_duration;
|
|
float eased = m_easingFunc(t);
|
|
m_current = lerp(m_start, m_target, eased);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Get current value
|
|
*/
|
|
const T& get() const { return m_current; }
|
|
|
|
/**
|
|
* @brief Get target value
|
|
*/
|
|
const T& getTarget() const { return m_target; }
|
|
|
|
/**
|
|
* @brief Check if currently animating
|
|
*/
|
|
bool isAnimating() const { return m_animating; }
|
|
|
|
/**
|
|
* @brief Get animation progress (0-1)
|
|
*/
|
|
float getProgress() const {
|
|
if (!m_animating) return 1.0f;
|
|
return m_elapsed / m_duration;
|
|
}
|
|
|
|
/**
|
|
* @brief Implicit conversion to value type
|
|
*/
|
|
operator const T&() const { return m_current; }
|
|
|
|
private:
|
|
T m_current;
|
|
T m_target;
|
|
T m_start;
|
|
float m_duration;
|
|
float m_elapsed;
|
|
EasingFunction m_easingFunc;
|
|
bool m_animating;
|
|
|
|
// Lerp specializations
|
|
static T lerp(const T& a, const T& b, float t) {
|
|
return a + (b - a) * t;
|
|
}
|
|
};
|
|
|
|
// Specialization for ImVec2
|
|
template<>
|
|
inline ImVec2 AnimatedValue<ImVec2>::lerp(const ImVec2& a, const ImVec2& b, float t) {
|
|
return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
|
|
}
|
|
|
|
// Specialization for ImVec4/color
|
|
template<>
|
|
inline ImVec4 AnimatedValue<ImVec4>::lerp(const ImVec4& a, const ImVec4& b, float t) {
|
|
return ImVec4(
|
|
a.x + (b.x - a.x) * t,
|
|
a.y + (b.y - a.y) * t,
|
|
a.z + (b.z - a.z) * t,
|
|
a.w + (b.w - a.w) * t
|
|
);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Animation Sequencer
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @brief Staggered animation for lists
|
|
*
|
|
* Creates staggered entrance animations for list items.
|
|
*/
|
|
class StaggerAnimation {
|
|
public:
|
|
StaggerAnimation(int itemCount, float staggerDelay = 0.05f,
|
|
float itemDuration = duration::EnterScreen)
|
|
: m_itemCount(itemCount)
|
|
, m_staggerDelay(staggerDelay)
|
|
, m_itemDuration(itemDuration)
|
|
, m_elapsed(0)
|
|
, m_running(false)
|
|
{}
|
|
|
|
/**
|
|
* @brief Start the stagger animation
|
|
*/
|
|
void start() {
|
|
m_elapsed = 0;
|
|
m_running = true;
|
|
}
|
|
|
|
/**
|
|
* @brief Update animation
|
|
*/
|
|
void update(float deltaTime) {
|
|
if (!m_running) return;
|
|
m_elapsed += deltaTime;
|
|
|
|
// Check if all items have finished
|
|
float totalDuration = m_staggerDelay * (m_itemCount - 1) + m_itemDuration;
|
|
if (m_elapsed >= totalDuration) {
|
|
m_running = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Get animation progress for a specific item
|
|
*
|
|
* @param itemIndex Item index (0-based)
|
|
* @return Progress 0.0-1.0 (clamped)
|
|
*/
|
|
float getItemProgress(int itemIndex) const {
|
|
if (!m_running && m_elapsed > 0)
|
|
return 1.0f; // Animation complete
|
|
if (itemIndex < 0 || itemIndex >= m_itemCount)
|
|
return 0.0f;
|
|
|
|
float itemStart = m_staggerDelay * itemIndex;
|
|
float itemElapsed = m_elapsed - itemStart;
|
|
|
|
if (itemElapsed <= 0) return 0.0f;
|
|
if (itemElapsed >= m_itemDuration) return 1.0f;
|
|
|
|
return EaseDecelerate(itemElapsed / m_itemDuration);
|
|
}
|
|
|
|
/**
|
|
* @brief Get eased alpha for item (for fade-in)
|
|
*/
|
|
float getItemAlpha(int itemIndex) const {
|
|
return getItemProgress(itemIndex);
|
|
}
|
|
|
|
/**
|
|
* @brief Get Y offset for item (for slide-in from bottom)
|
|
*/
|
|
float getItemYOffset(int itemIndex, float maxOffset = 20.0f) const {
|
|
float progress = getItemProgress(itemIndex);
|
|
return maxOffset * (1.0f - progress);
|
|
}
|
|
|
|
bool isRunning() const { return m_running; }
|
|
|
|
private:
|
|
int m_itemCount;
|
|
float m_staggerDelay;
|
|
float m_itemDuration;
|
|
float m_elapsed;
|
|
bool m_running;
|
|
};
|
|
|
|
// ============================================================================
|
|
// Container Transform
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @brief Container transform animation state
|
|
*
|
|
* For hero-style transitions where a card expands into a full dialog/page.
|
|
*/
|
|
struct ContainerTransform {
|
|
ImRect startRect; // Starting bounds (e.g., card)
|
|
ImRect endRect; // Ending bounds (e.g., dialog)
|
|
float progress; // 0 = start, 1 = end
|
|
bool expanding; // Direction
|
|
|
|
ContainerTransform()
|
|
: progress(0)
|
|
, expanding(true)
|
|
{}
|
|
|
|
/**
|
|
* @brief Get interpolated bounds at current progress
|
|
*/
|
|
ImRect getCurrentRect() const {
|
|
float t = expanding ? progress : (1.0f - progress);
|
|
float eased = EaseStandard(t);
|
|
|
|
return ImRect(
|
|
ImLerp(startRect.Min, endRect.Min, eased),
|
|
ImLerp(startRect.Max, endRect.Max, eased)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @brief Get corner radius (shrinks as container expands)
|
|
*/
|
|
float getCornerRadius(float startRadius, float endRadius) const {
|
|
float t = expanding ? progress : (1.0f - progress);
|
|
float eased = EaseStandard(t);
|
|
return startRadius + (endRadius - startRadius) * eased;
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// Implementation
|
|
// ============================================================================
|
|
|
|
inline float CubicBezier(float t, float x1, float y1, float x2, float y2) {
|
|
// Attempt to find t value for given x (Newton-Raphson approximation)
|
|
// This is needed because CSS bezier curves are defined in terms of x
|
|
|
|
// For simplicity, we'll use a direct parametric approach
|
|
// which is accurate enough for UI animations
|
|
|
|
float cx = 3.0f * x1;
|
|
float bx = 3.0f * (x2 - x1) - cx;
|
|
float ax = 1.0f - cx - bx;
|
|
|
|
float cy = 3.0f * y1;
|
|
float by = 3.0f * (y2 - y1) - cy;
|
|
float ay = 1.0f - cy - by;
|
|
|
|
// Sample y at parameter t
|
|
// Note: This assumes t directly maps to time, which is an approximation
|
|
// For more accuracy, we'd need to solve for the bezier parameter given x=t
|
|
|
|
float t2 = t * t;
|
|
float t3 = t2 * t;
|
|
|
|
return ay * t3 + by * t2 + cy * t;
|
|
}
|
|
|
|
inline float EaseStandard(float t) {
|
|
// cubic-bezier(0.4, 0.0, 0.2, 1.0)
|
|
return CubicBezier(t, 0.4f, 0.0f, 0.2f, 1.0f);
|
|
}
|
|
|
|
inline float EaseDecelerate(float t) {
|
|
// cubic-bezier(0.0, 0.0, 0.2, 1.0)
|
|
return CubicBezier(t, 0.0f, 0.0f, 0.2f, 1.0f);
|
|
}
|
|
|
|
inline float EaseAccelerate(float t) {
|
|
// cubic-bezier(0.4, 0.0, 1.0, 1.0)
|
|
return CubicBezier(t, 0.4f, 0.0f, 1.0f, 1.0f);
|
|
}
|
|
|
|
inline float EaseSharp(float t) {
|
|
// cubic-bezier(0.4, 0.0, 0.6, 1.0)
|
|
return CubicBezier(t, 0.4f, 0.0f, 0.6f, 1.0f);
|
|
}
|
|
|
|
inline float EaseLinear(float t) {
|
|
return t;
|
|
}
|
|
|
|
inline float EaseOvershoot(float t, float overshoot) {
|
|
// Back ease out
|
|
t = t - 1.0f;
|
|
return t * t * ((overshoot + 1.0f) * t + overshoot) + 1.0f;
|
|
}
|
|
|
|
inline float EaseElastic(float t) {
|
|
if (t == 0.0f || t == 1.0f) return t;
|
|
|
|
float p = 0.3f;
|
|
float s = p / 4.0f;
|
|
|
|
return std::pow(2.0f, -10.0f * t) * std::sin((t - s) * (2.0f * IM_PI) / p) + 1.0f;
|
|
}
|
|
|
|
} // namespace material
|
|
} // namespace ui
|
|
} // namespace dragonx
|