Files
whetstone_DSL/editor/src/AnimationUtils.h

142 lines
4.3 KiB
C
Raw Normal View History

2026-02-09 22:18:11 -07:00
#pragma once
#include "imgui.h"
#include <algorithm>
#include <cmath>
#include <string>
#include <unordered_map>
namespace AnimationUtils {
inline float clamp01(float v) {
return std::max(0.0f, std::min(1.0f, v));
}
inline float lerp(float a, float b, float t) {
return a + (b - a) * t;
}
inline float easeOutCubic(float t) {
t = clamp01(t);
float inv = 1.0f - t;
return 1.0f - inv * inv * inv;
}
inline float easeInOut(float t) {
t = clamp01(t);
return t < 0.5f ? 2.0f * t * t : 1.0f - std::pow(-2.0f * t + 2.0f, 2.0f) / 2.0f;
}
inline float approach(float current, float target, float delta) {
if (current < target) return std::min(target, current + delta);
return std::max(target, current - delta);
}
inline float blinkAlpha(double nowSeconds, float rateHz, bool reduceMotion) {
if (reduceMotion || rateHz <= 0.0f) return 1.0f;
const float phase = std::fmod((float)nowSeconds * rateHz, 1.0f);
const float wave = 0.5f + 0.5f * std::sin(phase * 6.2831853f);
return clamp01(wave);
}
inline float pulseAlpha(double nowSeconds, double startSeconds, float duration, bool reduceMotion) {
if (reduceMotion) return 1.0f;
if (startSeconds <= 0.0) return 0.0f;
float t = (float)((nowSeconds - startSeconds) / std::max(0.001f, duration));
if (t < 0.0f || t > 1.0f) return 0.0f;
float eased = easeOutCubic(t);
return 1.0f - eased;
}
struct TooltipState {
double hoverStart = -1.0;
double lastUpdate = 0.0;
float alpha = 0.0f;
bool hovered = false;
};
inline std::unordered_map<std::string, TooltipState>& tooltipStates() {
static std::unordered_map<std::string, TooltipState> states;
return states;
}
inline float tooltipAlpha(const std::string& id,
bool hovered,
double nowSeconds,
bool reduceMotion,
float delay = 0.45f,
float fadeIn = 0.12f,
float fadeOut = 0.12f) {
if (reduceMotion) return hovered ? 1.0f : 0.0f;
auto& state = tooltipStates()[id];
if (state.lastUpdate <= 0.0) state.lastUpdate = nowSeconds;
const float dt = (float)(nowSeconds - state.lastUpdate);
state.lastUpdate = nowSeconds;
if (hovered) {
if (!state.hovered) {
state.hovered = true;
state.hoverStart = nowSeconds;
}
float target = (nowSeconds - state.hoverStart) >= delay ? 1.0f : 0.0f;
float step = (target > state.alpha)
? (dt / std::max(0.001f, fadeIn))
: (dt / std::max(0.001f, fadeOut));
state.alpha = approach(state.alpha, target, step);
} else {
if (state.hovered) {
state.hovered = false;
}
float step = dt / std::max(0.001f, fadeOut);
state.alpha = approach(state.alpha, 0.0f, step);
}
return clamp01(state.alpha);
}
struct PanelState {
bool visible = false;
double toggleTime = 0.0;
bool animating = false;
};
inline std::unordered_map<std::string, PanelState>& panelStates() {
static std::unordered_map<std::string, PanelState> states;
return states;
}
inline bool panelTransition(const std::string& id,
bool targetVisible,
double nowSeconds,
bool reduceMotion,
float duration,
float slideDistance,
float& outAlpha,
float& outOffset) {
auto& state = panelStates()[id];
if (state.visible != targetVisible) {
state.visible = targetVisible;
state.toggleTime = nowSeconds;
state.animating = true;
}
if (reduceMotion) {
outAlpha = targetVisible ? 1.0f : 0.0f;
outOffset = 0.0f;
state.animating = false;
return targetVisible;
}
float t = (float)((nowSeconds - state.toggleTime) / std::max(0.001f, duration));
if (t >= 1.0f) {
t = 1.0f;
state.animating = false;
}
if (state.visible) {
outAlpha = easeOutCubic(t);
outOffset = (1.0f - outAlpha) * slideDistance;
} else {
outAlpha = 1.0f - easeOutCubic(t);
outOffset = (1.0f - outAlpha) * slideDistance;
}
return state.visible || state.animating;
}
} // namespace AnimationUtils