mirror of
https://github.com/hyprwm/hyprutils.git
synced 2025-02-05 08:09:48 +01:00
animation: add BezierCurve, AnimationManager and AnimatedVariable (#27)
This commit is contained in:
parent
8f15d45b12
commit
8af7e4b9de
8 changed files with 841 additions and 0 deletions
|
@ -94,6 +94,14 @@ add_test(
|
|||
COMMAND hyprutils_filedescriptor "filedescriptor")
|
||||
add_dependencies(tests hyprutils_filedescriptor)
|
||||
|
||||
add_executable(hyprutils_animation "tests/animation.cpp")
|
||||
target_link_libraries(hyprutils_animation PRIVATE hyprutils PkgConfig::deps)
|
||||
add_test(
|
||||
NAME "Animation"
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
|
||||
COMMAND hyprutils_animation "utils")
|
||||
add_dependencies(tests hyprutils_animation)
|
||||
|
||||
# Installation
|
||||
install(TARGETS hyprutils)
|
||||
install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
|
|
230
include/hyprutils/animation/AnimatedVariable.hpp
Normal file
230
include/hyprutils/animation/AnimatedVariable.hpp
Normal file
|
@ -0,0 +1,230 @@
|
|||
#pragma once
|
||||
|
||||
#include "../memory/WeakPtr.hpp"
|
||||
#include "hyprutils/memory/SharedPtr.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Animation {
|
||||
class CAnimationManager;
|
||||
|
||||
/*
|
||||
Structure for animation properties.
|
||||
Config properties need to have a static lifetime to allow for config reload.
|
||||
*/
|
||||
struct SAnimationPropertyConfig {
|
||||
bool overridden = true;
|
||||
|
||||
std::string internalBezier = "";
|
||||
std::string internalStyle = "";
|
||||
float internalSpeed = 0.f;
|
||||
int internalEnabled = -1;
|
||||
|
||||
Memory::CWeakPointer<SAnimationPropertyConfig> pValues;
|
||||
Memory::CWeakPointer<SAnimationPropertyConfig> pParentAnimation;
|
||||
};
|
||||
|
||||
/* A base class for animated variables. */
|
||||
class CBaseAnimatedVariable {
|
||||
public:
|
||||
using CallbackFun = std::function<void(Memory::CWeakPointer<CBaseAnimatedVariable> thisptr)>;
|
||||
|
||||
CBaseAnimatedVariable() {
|
||||
; // m_bDummy = true;
|
||||
};
|
||||
|
||||
void create(CAnimationManager*, int, Memory::CSharedPointer<CBaseAnimatedVariable>);
|
||||
void connectToActive();
|
||||
void disconnectFromActive();
|
||||
|
||||
/* Needs to call disconnectFromActive to remove `m_pSelf` from the active animation list */
|
||||
virtual ~CBaseAnimatedVariable() {
|
||||
disconnectFromActive();
|
||||
};
|
||||
|
||||
virtual void warp(bool endCallback = true) = 0;
|
||||
|
||||
CBaseAnimatedVariable(const CBaseAnimatedVariable&) = delete;
|
||||
CBaseAnimatedVariable(CBaseAnimatedVariable&&) = delete;
|
||||
CBaseAnimatedVariable& operator=(const CBaseAnimatedVariable&) = delete;
|
||||
CBaseAnimatedVariable& operator=(CBaseAnimatedVariable&&) = delete;
|
||||
|
||||
void setConfig(Memory::CSharedPointer<SAnimationPropertyConfig> pConfig) {
|
||||
m_pConfig = pConfig;
|
||||
}
|
||||
|
||||
Memory::CWeakPointer<SAnimationPropertyConfig> getConfig() const {
|
||||
return m_pConfig;
|
||||
}
|
||||
|
||||
bool enabled() const;
|
||||
const std::string& getBezierName() const;
|
||||
const std::string& getStyle() const;
|
||||
|
||||
/* returns the spent (completion) % */
|
||||
float getPercent() const;
|
||||
|
||||
/* returns the current curve value */
|
||||
float getCurveValue() const;
|
||||
|
||||
/* checks if an animation is in progress */
|
||||
bool isBeingAnimated() const {
|
||||
return m_bIsBeingAnimated;
|
||||
}
|
||||
|
||||
/* checks m_bDummy and m_pAnimationManager */
|
||||
bool ok() const;
|
||||
|
||||
/* calls the update callback */
|
||||
void onUpdate();
|
||||
|
||||
/* sets a function to be ran when an animation ended.
|
||||
if "remove" is set to true, it will remove the callback when ran. */
|
||||
void setCallbackOnEnd(CallbackFun func, bool remove = true);
|
||||
|
||||
/* sets a function to be ran when an animation is started.
|
||||
if "remove" is set to true, it will remove the callback when ran. */
|
||||
void setCallbackOnBegin(CallbackFun func, bool remove = true);
|
||||
|
||||
/* sets the update callback, called every time the value is animated and a step is done
|
||||
Warning: calling unregisterVar/registerVar in this handler will cause UB */
|
||||
void setUpdateCallback(CallbackFun func);
|
||||
|
||||
/* resets all callbacks. Does not call any. */
|
||||
void resetAllCallbacks();
|
||||
|
||||
void onAnimationEnd();
|
||||
void onAnimationBegin();
|
||||
|
||||
int m_Type = -1;
|
||||
|
||||
protected:
|
||||
friend class CAnimationManager;
|
||||
|
||||
bool m_bIsConnectedToActive = false;
|
||||
bool m_bIsBeingAnimated = false;
|
||||
|
||||
Memory::CWeakPointer<CBaseAnimatedVariable> m_pSelf;
|
||||
|
||||
private:
|
||||
Memory::CWeakPointer<SAnimationPropertyConfig> m_pConfig;
|
||||
|
||||
std::chrono::steady_clock::time_point animationBegin;
|
||||
|
||||
bool m_bDummy = true;
|
||||
|
||||
CAnimationManager* m_pAnimationManager = nullptr;
|
||||
bool m_bRemoveEndAfterRan = true;
|
||||
bool m_bRemoveBeginAfterRan = true;
|
||||
|
||||
CallbackFun m_fEndCallback;
|
||||
CallbackFun m_fBeginCallback;
|
||||
CallbackFun m_fUpdateCallback;
|
||||
};
|
||||
|
||||
/* This concept represents the minimum requirement for a type to be used with CGenericAnimatedVariable */
|
||||
template <class ValueImpl>
|
||||
concept AnimatedType = requires(ValueImpl val) {
|
||||
requires std::is_copy_constructible_v<ValueImpl>;
|
||||
{ val == val } -> std::same_as<bool>; // requires operator==
|
||||
{ val = val }; // requires operator=
|
||||
};
|
||||
|
||||
/*
|
||||
A generic class for variables.
|
||||
VarType is the type of the variable to be animated.
|
||||
AnimationContext is there to attach additional data to the animation.
|
||||
In Hyprland that struct would contain a reference to window, workspace or layer for example.
|
||||
*/
|
||||
template <AnimatedType VarType, class AnimationContext>
|
||||
class CGenericAnimatedVariable : public CBaseAnimatedVariable {
|
||||
public:
|
||||
CGenericAnimatedVariable() = default;
|
||||
|
||||
void create(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CSharedPointer<CGenericAnimatedVariable<VarType, AnimationContext>> pSelf,
|
||||
const VarType& initialValue) {
|
||||
m_Begun = initialValue;
|
||||
m_Value = initialValue;
|
||||
m_Goal = initialValue;
|
||||
|
||||
CBaseAnimatedVariable::create(pAnimationManager, typeInfo, pSelf);
|
||||
}
|
||||
|
||||
CGenericAnimatedVariable(const CGenericAnimatedVariable&) = delete;
|
||||
CGenericAnimatedVariable(CGenericAnimatedVariable&&) = delete;
|
||||
CGenericAnimatedVariable& operator=(const CGenericAnimatedVariable&) = delete;
|
||||
CGenericAnimatedVariable& operator=(CGenericAnimatedVariable&&) = delete;
|
||||
|
||||
virtual void warp(bool endCallback = true) {
|
||||
if (!m_bIsBeingAnimated)
|
||||
return;
|
||||
|
||||
m_Value = m_Goal;
|
||||
|
||||
m_bIsBeingAnimated = false;
|
||||
|
||||
onUpdate();
|
||||
|
||||
if (endCallback)
|
||||
onAnimationEnd();
|
||||
}
|
||||
|
||||
const VarType& value() const {
|
||||
return m_Value;
|
||||
}
|
||||
|
||||
/* used to update the value each tick via the AnimationManager */
|
||||
VarType& value() {
|
||||
return m_Value;
|
||||
}
|
||||
|
||||
const VarType& goal() const {
|
||||
return m_Goal;
|
||||
}
|
||||
|
||||
const VarType& begun() const {
|
||||
return m_Begun;
|
||||
}
|
||||
|
||||
CGenericAnimatedVariable& operator=(const VarType& v) {
|
||||
if (v == m_Goal)
|
||||
return *this;
|
||||
|
||||
m_Goal = v;
|
||||
m_Begun = m_Value;
|
||||
|
||||
onAnimationBegin();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Sets the actual stored value, without affecting the goal, but resets the timer*/
|
||||
void setValue(const VarType& v) {
|
||||
if (v == m_Value)
|
||||
return;
|
||||
|
||||
m_Value = v;
|
||||
m_Begun = m_Value;
|
||||
|
||||
onAnimationBegin();
|
||||
}
|
||||
|
||||
/* Sets the actual value and goal*/
|
||||
void setValueAndWarp(const VarType& v) {
|
||||
m_Goal = v;
|
||||
m_bIsBeingAnimated = true;
|
||||
|
||||
warp();
|
||||
}
|
||||
|
||||
AnimationContext m_Context;
|
||||
|
||||
private:
|
||||
VarType m_Value{};
|
||||
VarType m_Goal{};
|
||||
VarType m_Begun{};
|
||||
};
|
||||
}
|
||||
}
|
40
include/hyprutils/animation/AnimationManager.hpp
Normal file
40
include/hyprutils/animation/AnimationManager.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include "./BezierCurve.hpp"
|
||||
#include "./AnimatedVariable.hpp"
|
||||
#include "../math/Vector2D.hpp"
|
||||
#include "../memory/WeakPtr.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Animation {
|
||||
/* A class for managing bezier curves and variables that are being animated. */
|
||||
class CAnimationManager {
|
||||
public:
|
||||
CAnimationManager();
|
||||
|
||||
void tickDone();
|
||||
bool shouldTickForNext();
|
||||
|
||||
virtual void scheduleTick() = 0;
|
||||
virtual void onTicked() = 0;
|
||||
|
||||
void addBezierWithName(std::string, const Math::Vector2D&, const Math::Vector2D&);
|
||||
void removeAllBeziers();
|
||||
|
||||
bool bezierExists(const std::string&);
|
||||
Memory::CSharedPointer<CBezierCurve> getBezier(const std::string&);
|
||||
|
||||
const std::unordered_map<std::string, Memory::CSharedPointer<CBezierCurve>>& getAllBeziers();
|
||||
|
||||
std::vector<Memory::CWeakPointer<CBaseAnimatedVariable>> m_vActiveAnimatedVariables;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, Memory::CSharedPointer<CBezierCurve>> m_mBezierCurves;
|
||||
|
||||
bool m_bTickScheduled = false;
|
||||
};
|
||||
}
|
||||
}
|
30
include/hyprutils/animation/BezierCurve.hpp
Normal file
30
include/hyprutils/animation/BezierCurve.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "../math/Vector2D.hpp"
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Animation {
|
||||
constexpr int BAKEDPOINTS = 255;
|
||||
constexpr float INVBAKEDPOINTS = 1.f / BAKEDPOINTS;
|
||||
|
||||
/* An implementation of a cubic bezier curve. */
|
||||
class CBezierCurve {
|
||||
public:
|
||||
/* Calculates a cubic bezier curve based on 2 control points (EXCLUDES the 0,0 and 1,1 points). */
|
||||
void setup(const std::array<Hyprutils::Math::Vector2D, 2>& points);
|
||||
|
||||
float getYForT(float const& t) const;
|
||||
float getXForT(float const& t) const;
|
||||
float getYForPoint(float const& x) const;
|
||||
|
||||
private:
|
||||
/* this INCLUDES the 0,0 and 1,1 points. */
|
||||
std::vector<Hyprutils::Math::Vector2D> m_vPoints;
|
||||
|
||||
std::array<Hyprutils::Math::Vector2D, BAKEDPOINTS> m_aPointsBaked;
|
||||
};
|
||||
}
|
||||
}
|
158
src/animation/AnimatedVariable.cpp
Normal file
158
src/animation/AnimatedVariable.cpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
#include <hyprutils/animation/AnimatedVariable.hpp>
|
||||
#include <hyprutils/animation/AnimationManager.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
|
||||
using namespace Hyprutils::Animation;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
|
||||
void CBaseAnimatedVariable::create(Hyprutils::Animation::CAnimationManager* pAnimationManager, int typeInfo, SP<CBaseAnimatedVariable> pSelf) {
|
||||
m_pAnimationManager = pAnimationManager;
|
||||
m_Type = typeInfo;
|
||||
m_pSelf = pSelf;
|
||||
|
||||
m_bDummy = false;
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::connectToActive() {
|
||||
if (!m_pAnimationManager || m_bDummy)
|
||||
return;
|
||||
|
||||
m_pAnimationManager->scheduleTick(); // otherwise the animation manager will never pick this up
|
||||
if (!m_bIsConnectedToActive)
|
||||
m_pAnimationManager->m_vActiveAnimatedVariables.push_back(m_pSelf);
|
||||
m_bIsConnectedToActive = true;
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::disconnectFromActive() {
|
||||
if (!m_pAnimationManager)
|
||||
return;
|
||||
|
||||
std::erase_if(m_pAnimationManager->m_vActiveAnimatedVariables, [&](const auto& other) { return other == m_pSelf; });
|
||||
m_bIsConnectedToActive = false;
|
||||
}
|
||||
|
||||
bool Hyprutils::Animation::CBaseAnimatedVariable::enabled() const {
|
||||
if (const auto PCONFIG = m_pConfig.lock()) {
|
||||
const auto PVALUES = PCONFIG->pValues.lock();
|
||||
return PVALUES ? PVALUES->internalEnabled : false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& CBaseAnimatedVariable::getBezierName() const {
|
||||
static constexpr const std::string DEFAULTBEZIERNAME = "default";
|
||||
|
||||
if (const auto PCONFIG = m_pConfig.lock()) {
|
||||
const auto PVALUES = PCONFIG->pValues.lock();
|
||||
return PVALUES ? PVALUES->internalBezier : DEFAULTBEZIERNAME;
|
||||
}
|
||||
|
||||
return DEFAULTBEZIERNAME;
|
||||
}
|
||||
|
||||
const std::string& CBaseAnimatedVariable::getStyle() const {
|
||||
static constexpr const std::string DEFAULTSTYLE = "";
|
||||
|
||||
if (const auto PCONFIG = m_pConfig.lock()) {
|
||||
const auto PVALUES = PCONFIG->pValues.lock();
|
||||
return PVALUES ? PVALUES->internalStyle : DEFAULTSTYLE;
|
||||
}
|
||||
|
||||
return DEFAULTSTYLE;
|
||||
}
|
||||
|
||||
float CBaseAnimatedVariable::getPercent() const {
|
||||
const auto DURATIONPASSED = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - animationBegin).count();
|
||||
|
||||
if (const auto PCONFIG = m_pConfig.lock()) {
|
||||
const auto PVALUES = PCONFIG->pValues.lock();
|
||||
return PVALUES ? std::clamp((DURATIONPASSED / 100.f) / PVALUES->internalSpeed, 0.f, 1.f) : 1.f;
|
||||
}
|
||||
|
||||
return 1.f;
|
||||
}
|
||||
|
||||
float CBaseAnimatedVariable::getCurveValue() const {
|
||||
if (!m_bIsBeingAnimated || !m_pAnimationManager)
|
||||
return 1.f;
|
||||
|
||||
std::string bezierName = "";
|
||||
if (const auto PCONFIG = m_pConfig.lock()) {
|
||||
const auto PVALUES = PCONFIG->pValues.lock();
|
||||
if (PVALUES)
|
||||
bezierName = PVALUES->internalBezier;
|
||||
}
|
||||
|
||||
const auto BEZIER = m_pAnimationManager->getBezier(bezierName);
|
||||
if (!BEZIER)
|
||||
return 1.f;
|
||||
|
||||
const auto SPENT = getPercent();
|
||||
if (SPENT >= 1.f)
|
||||
return 1.f;
|
||||
|
||||
return BEZIER->getYForPoint(SPENT);
|
||||
}
|
||||
|
||||
bool CBaseAnimatedVariable::ok() const {
|
||||
return m_pConfig && m_pAnimationManager;
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::onUpdate() {
|
||||
if (m_fUpdateCallback)
|
||||
m_fUpdateCallback(m_pSelf);
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::setCallbackOnEnd(CallbackFun func, bool remove) {
|
||||
m_fEndCallback = std::move(func);
|
||||
m_bRemoveEndAfterRan = remove;
|
||||
|
||||
if (!isBeingAnimated())
|
||||
onAnimationEnd();
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::setCallbackOnBegin(CallbackFun func, bool remove) {
|
||||
m_fBeginCallback = std::move(func);
|
||||
m_bRemoveBeginAfterRan = remove;
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::setUpdateCallback(CallbackFun func) {
|
||||
m_fUpdateCallback = std::move(func);
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::resetAllCallbacks() {
|
||||
m_fBeginCallback = nullptr;
|
||||
m_fEndCallback = nullptr;
|
||||
m_fUpdateCallback = nullptr;
|
||||
m_bRemoveBeginAfterRan = false;
|
||||
m_bRemoveEndAfterRan = false;
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::onAnimationEnd() {
|
||||
m_bIsBeingAnimated = false;
|
||||
/* We do not call disconnectFromActive here. The animation manager will remove it on a call to tickDone. */
|
||||
|
||||
if (m_fEndCallback) {
|
||||
/* loading m_bRemoveEndAfterRan before calling the callback allows the callback to delete this animation safely if it is false. */
|
||||
auto removeEndCallback = m_bRemoveEndAfterRan;
|
||||
m_fEndCallback(m_pSelf);
|
||||
if (removeEndCallback)
|
||||
m_fEndCallback = nullptr; // reset
|
||||
}
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::onAnimationBegin() {
|
||||
m_bIsBeingAnimated = true;
|
||||
animationBegin = std::chrono::steady_clock::now();
|
||||
connectToActive();
|
||||
|
||||
if (m_fBeginCallback) {
|
||||
m_fBeginCallback(m_pSelf);
|
||||
if (m_bRemoveBeginAfterRan)
|
||||
m_fBeginCallback = nullptr; // reset
|
||||
}
|
||||
}
|
73
src/animation/AnimationManager.cpp
Normal file
73
src/animation/AnimationManager.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include <hyprutils/animation/AnimationManager.hpp>
|
||||
|
||||
using namespace Hyprutils::Animation;
|
||||
using namespace Hyprutils::Math;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define SP CSharedPointer
|
||||
|
||||
const std::array<Vector2D, 2> DEFAULTBEZIERPOINTS = {Vector2D(0.0, 0.75), Vector2D(0.15, 1.0)};
|
||||
|
||||
CAnimationManager::CAnimationManager() {
|
||||
const auto BEZIER = makeShared<CBezierCurve>();
|
||||
BEZIER->setup(DEFAULTBEZIERPOINTS);
|
||||
m_mBezierCurves["default"] = BEZIER;
|
||||
}
|
||||
|
||||
void CAnimationManager::removeAllBeziers() {
|
||||
m_mBezierCurves.clear();
|
||||
|
||||
// add the default one
|
||||
const auto BEZIER = makeShared<CBezierCurve>();
|
||||
BEZIER->setup(DEFAULTBEZIERPOINTS);
|
||||
m_mBezierCurves["default"] = BEZIER;
|
||||
}
|
||||
|
||||
void CAnimationManager::addBezierWithName(std::string name, const Vector2D& p1, const Vector2D& p2) {
|
||||
const auto BEZIER = makeShared<CBezierCurve>();
|
||||
BEZIER->setup({
|
||||
p1,
|
||||
p2,
|
||||
});
|
||||
m_mBezierCurves[name] = BEZIER;
|
||||
}
|
||||
|
||||
bool CAnimationManager::shouldTickForNext() {
|
||||
return !m_vActiveAnimatedVariables.empty();
|
||||
}
|
||||
|
||||
void CAnimationManager::tickDone() {
|
||||
std::vector<CWeakPointer<CBaseAnimatedVariable>> active;
|
||||
active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations
|
||||
for (auto const& av : m_vActiveAnimatedVariables) {
|
||||
const auto PAV = av.lock();
|
||||
if (!PAV)
|
||||
continue;
|
||||
|
||||
if (PAV->ok() && PAV->isBeingAnimated())
|
||||
active.emplace_back(av);
|
||||
else
|
||||
PAV->m_bIsConnectedToActive = false;
|
||||
}
|
||||
|
||||
m_vActiveAnimatedVariables = std::move(active);
|
||||
}
|
||||
|
||||
bool CAnimationManager::bezierExists(const std::string& bezier) {
|
||||
for (auto const& [bc, bz] : m_mBezierCurves) {
|
||||
if (bc == bezier)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SP<CBezierCurve> CAnimationManager::getBezier(const std::string& name) {
|
||||
const auto BEZIER = std::find_if(m_mBezierCurves.begin(), m_mBezierCurves.end(), [&](const auto& other) { return other.first == name; });
|
||||
|
||||
return BEZIER == m_mBezierCurves.end() ? m_mBezierCurves["default"] : BEZIER->second;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, SP<CBezierCurve>>& CAnimationManager::getAllBeziers() {
|
||||
return m_mBezierCurves;
|
||||
}
|
78
src/animation/BezierCurve.cpp
Normal file
78
src/animation/BezierCurve.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
#include <hyprutils/animation/BezierCurve.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
using namespace Hyprutils::Animation;
|
||||
using namespace Hyprutils::Math;
|
||||
|
||||
void CBezierCurve::setup(const std::array<Vector2D, 2>& pVec) {
|
||||
// Avoid reallocations by reserving enough memory upfront
|
||||
m_vPoints.resize(pVec.size() + 2);
|
||||
m_vPoints = {
|
||||
Vector2D(0, 0), // Start point
|
||||
pVec[0], pVec[1], // Control points
|
||||
Vector2D(1, 1) // End point
|
||||
};
|
||||
|
||||
if (m_vPoints.size() != 4)
|
||||
std::abort();
|
||||
|
||||
// bake BAKEDPOINTS points for faster lookups
|
||||
// T -> X ( / BAKEDPOINTS )
|
||||
for (int i = 0; i < BAKEDPOINTS; ++i) {
|
||||
float const t = (i + 1) / (float)BAKEDPOINTS;
|
||||
m_aPointsBaked[i] = Vector2D(getXForT(t), getYForT(t));
|
||||
}
|
||||
|
||||
for (int j = 1; j < 10; ++j) {
|
||||
float i = j / 10.0f;
|
||||
getYForPoint(i);
|
||||
}
|
||||
}
|
||||
|
||||
float CBezierCurve::getXForT(float const& t) const {
|
||||
float t2 = t * t;
|
||||
float t3 = t2 * t;
|
||||
|
||||
return 3 * t * (1 - t) * (1 - t) * m_vPoints[1].x + 3 * t2 * (1 - t) * m_vPoints[2].x + t3 * m_vPoints[3].x;
|
||||
}
|
||||
|
||||
float CBezierCurve::getYForT(float const& t) const {
|
||||
float t2 = t * t;
|
||||
float t3 = t2 * t;
|
||||
|
||||
return 3 * t * (1 - t) * (1 - t) * m_vPoints[1].y + 3 * t2 * (1 - t) * m_vPoints[2].y + t3 * m_vPoints[3].y;
|
||||
}
|
||||
|
||||
// Todo: this probably can be done better and faster
|
||||
float CBezierCurve::getYForPoint(float const& x) const {
|
||||
if (x >= 1.f)
|
||||
return 1.f;
|
||||
if (x <= 0.f)
|
||||
return 0.f;
|
||||
|
||||
int index = 0;
|
||||
bool below = true;
|
||||
for (int step = (BAKEDPOINTS + 1) / 2; step > 0; step /= 2) {
|
||||
if (below)
|
||||
index += step;
|
||||
else
|
||||
index -= step;
|
||||
|
||||
below = m_aPointsBaked[index].x < x;
|
||||
}
|
||||
|
||||
int lowerIndex = index - (!below || index == BAKEDPOINTS - 1);
|
||||
|
||||
// in the name of performance i shall make a hack
|
||||
const auto LOWERPOINT = &m_aPointsBaked[lowerIndex];
|
||||
const auto UPPERPOINT = &m_aPointsBaked[lowerIndex + 1];
|
||||
|
||||
const auto PERCINDELTA = (x - LOWERPOINT->x) / (UPPERPOINT->x - LOWERPOINT->x);
|
||||
|
||||
if (std::isnan(PERCINDELTA) || std::isinf(PERCINDELTA)) // can sometimes happen for VERY small x
|
||||
return 0.f;
|
||||
|
||||
return LOWERPOINT->y + (UPPERPOINT->y - LOWERPOINT->y) * PERCINDELTA;
|
||||
}
|
224
tests/animation.cpp
Normal file
224
tests/animation.cpp
Normal file
|
@ -0,0 +1,224 @@
|
|||
#include <hyprutils/animation/AnimationManager.hpp>
|
||||
#include <hyprutils/animation/AnimatedVariable.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include "shared.hpp"
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
|
||||
using namespace Hyprutils::Animation;
|
||||
using namespace Hyprutils::Math;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
class EmtpyContext {};
|
||||
|
||||
template <typename VarType>
|
||||
using CAnimatedVariable = CGenericAnimatedVariable<VarType, EmtpyContext>;
|
||||
|
||||
template <typename VarType>
|
||||
using PANIMVAR = SP<CAnimatedVariable<VarType>>;
|
||||
|
||||
template <typename VarType>
|
||||
using PANIMVARREF = WP<CAnimatedVariable<VarType>>;
|
||||
|
||||
enum eAVTypes {
|
||||
INT = 1,
|
||||
TEST,
|
||||
};
|
||||
|
||||
struct SomeTestType {
|
||||
bool done = false;
|
||||
bool operator==(const SomeTestType& other) const {
|
||||
return done == other.done;
|
||||
}
|
||||
SomeTestType& operator=(const SomeTestType& other) {
|
||||
done = other.done;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, SP<SAnimationPropertyConfig>> animationConfig;
|
||||
|
||||
class CMyAnimationManager : public CAnimationManager {
|
||||
public:
|
||||
void tick() {
|
||||
for (auto const& av : m_vActiveAnimatedVariables) {
|
||||
const auto PAV = av.lock();
|
||||
if (!PAV || !PAV->ok())
|
||||
continue;
|
||||
|
||||
const auto SPENT = PAV->getPercent();
|
||||
const auto PBEZIER = getBezier(PAV->getBezierName());
|
||||
const auto POINTY = PBEZIER->getYForPoint(SPENT);
|
||||
|
||||
if (POINTY >= 1.f || !PAV->enabled()) {
|
||||
PAV->warp();
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (PAV->m_Type) {
|
||||
case eAVTypes::INT: {
|
||||
auto avInt = dynamic_cast<CAnimatedVariable<int>*>(PAV.get());
|
||||
if (!avInt)
|
||||
std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET;
|
||||
|
||||
const auto DELTA = avInt->goal() - avInt->value();
|
||||
avInt->value() = avInt->begun() + (DELTA * POINTY);
|
||||
} break;
|
||||
case eAVTypes::TEST: {
|
||||
auto avCustom = dynamic_cast<CAnimatedVariable<SomeTestType>*>(PAV.get());
|
||||
if (!avCustom)
|
||||
std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET;
|
||||
|
||||
if (SPENT >= 1.f)
|
||||
avCustom->value().done = true;
|
||||
} break;
|
||||
default: {
|
||||
std::cout << Colors::RED << "What are we even doing?" << Colors::RESET;
|
||||
} break;
|
||||
}
|
||||
|
||||
av->onUpdate();
|
||||
}
|
||||
|
||||
tickDone();
|
||||
}
|
||||
|
||||
template <typename VarType>
|
||||
void createAnimation(const VarType& v, PANIMVAR<VarType>& av, const std::string& animationConfigName) {
|
||||
constexpr const eAVTypes EAVTYPE = std::is_same_v<VarType, int> ? eAVTypes::INT : eAVTypes::TEST;
|
||||
const auto PAV = makeShared<CGenericAnimatedVariable<VarType, EmtpyContext>>();
|
||||
|
||||
PAV->create(EAVTYPE, static_cast<CAnimationManager*>(this), PAV, v);
|
||||
PAV->setConfig(animationConfig[animationConfigName]);
|
||||
av = std::move(PAV);
|
||||
}
|
||||
|
||||
virtual void scheduleTick() {
|
||||
;
|
||||
}
|
||||
|
||||
virtual void onTicked() {
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
CMyAnimationManager gAnimationManager;
|
||||
|
||||
class Subject {
|
||||
public:
|
||||
Subject(const int& a, const int& b) {
|
||||
gAnimationManager.createAnimation(a, m_iA, "default");
|
||||
gAnimationManager.createAnimation(b, m_iB, "default");
|
||||
gAnimationManager.createAnimation({}, m_iC, "default");
|
||||
}
|
||||
PANIMVAR<int> m_iA;
|
||||
PANIMVAR<int> m_iB;
|
||||
PANIMVAR<SomeTestType> m_iC;
|
||||
};
|
||||
|
||||
int main(int argc, char** argv, char** envp) {
|
||||
animationConfig["default"] = makeShared<SAnimationPropertyConfig>();
|
||||
animationConfig["default"]->internalBezier = "default";
|
||||
animationConfig["default"]->internalSpeed = 1.0;
|
||||
animationConfig["default"]->internalStyle = "asdf";
|
||||
animationConfig["default"]->internalEnabled = 1;
|
||||
animationConfig["default"]->pValues = animationConfig["default"];
|
||||
|
||||
int ret = 0;
|
||||
Subject s(0, 0);
|
||||
|
||||
EXPECT(s.m_iA->value(), 0);
|
||||
EXPECT(s.m_iB->value(), 0);
|
||||
|
||||
// Test destruction of a CAnimatedVariable
|
||||
{
|
||||
Subject s2(10, 10);
|
||||
// Adds them to active
|
||||
*s2.m_iA = 1;
|
||||
*s2.m_iB = 2;
|
||||
// We deliberately do not tick here, to make sure the destructor removes active animated variables
|
||||
}
|
||||
|
||||
EXPECT(gAnimationManager.shouldTickForNext(), false);
|
||||
EXPECT(s.m_iC->value().done, false);
|
||||
|
||||
*s.m_iA = 10;
|
||||
*s.m_iB = 100;
|
||||
*s.m_iC = SomeTestType(true);
|
||||
|
||||
EXPECT(s.m_iC->value().done, false);
|
||||
|
||||
while (gAnimationManager.shouldTickForNext()) {
|
||||
gAnimationManager.tick();
|
||||
}
|
||||
|
||||
EXPECT(s.m_iA->value(), 10);
|
||||
EXPECT(s.m_iB->value(), 100);
|
||||
EXPECT(s.m_iC->value().done, true);
|
||||
|
||||
s.m_iA->setValue(0);
|
||||
s.m_iB->setValue(0);
|
||||
|
||||
while (gAnimationManager.shouldTickForNext()) {
|
||||
gAnimationManager.tick();
|
||||
}
|
||||
|
||||
EXPECT(s.m_iA->value(), 10);
|
||||
EXPECT(s.m_iB->value(), 100);
|
||||
|
||||
// Test config stuff
|
||||
EXPECT(s.m_iA->getBezierName(), "default");
|
||||
EXPECT(s.m_iA->getStyle(), "asdf");
|
||||
EXPECT(s.m_iA->enabled(), true);
|
||||
|
||||
animationConfig["default"]->internalEnabled = 0;
|
||||
|
||||
EXPECT(s.m_iA->enabled(), false);
|
||||
|
||||
*s.m_iA = 50;
|
||||
gAnimationManager.tick(); // Expecting a warp
|
||||
EXPECT(s.m_iA->value(), 50);
|
||||
|
||||
// Test missing pValues
|
||||
animationConfig["default"]->internalEnabled = 1;
|
||||
animationConfig["default"]->pValues.reset();
|
||||
|
||||
EXPECT(s.m_iA->enabled(), false);
|
||||
EXPECT(s.m_iA->getBezierName(), "default");
|
||||
EXPECT(s.m_iA->getStyle(), "");
|
||||
EXPECT(s.m_iA->getPercent(), 1.f);
|
||||
|
||||
animationConfig["default"]->pValues = animationConfig["default"];
|
||||
|
||||
//
|
||||
// Test callbacks
|
||||
//
|
||||
bool beginCallbackRan = false;
|
||||
bool updateCallbackRan = false;
|
||||
bool endCallbackRan = false;
|
||||
s.m_iA->setCallbackOnBegin([&beginCallbackRan](WP<CBaseAnimatedVariable> pav) { beginCallbackRan = true; });
|
||||
s.m_iA->setUpdateCallback([&updateCallbackRan](WP<CBaseAnimatedVariable> pav) { updateCallbackRan = true; });
|
||||
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> pav) { endCallbackRan = true; }, false);
|
||||
|
||||
s.m_iA->setValueAndWarp(42);
|
||||
|
||||
EXPECT(beginCallbackRan, false);
|
||||
EXPECT(updateCallbackRan, true);
|
||||
EXPECT(endCallbackRan, true);
|
||||
|
||||
beginCallbackRan = false;
|
||||
updateCallbackRan = false;
|
||||
endCallbackRan = false;
|
||||
|
||||
*s.m_iA = 1337;
|
||||
while (gAnimationManager.shouldTickForNext()) {
|
||||
gAnimationManager.tick();
|
||||
}
|
||||
|
||||
EXPECT(beginCallbackRan, true);
|
||||
EXPECT(updateCallbackRan, true);
|
||||
EXPECT(endCallbackRan, true);
|
||||
|
||||
return ret;
|
||||
}
|
Loading…
Reference in a new issue