Files
ObsidianDragon/src/ui/material/motion.h
DanS 3aee55b49c 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)
2026-02-27 00:26:01 -06:00

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