deargui-vpl/imgui_node_editor_animation.cpp

374 lines
9.1 KiB
C++

//------------------------------------------------------------------------------
// VERSION 0.9.1
//
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
//------------------------------------------------------------------------------
# include "imgui_node_editor_internal.h"
# include <cstdio>
# include <string>
# include <fstream>
# include <bitset>
# include <climits>
# include <algorithm>
# include <sstream>
# include <streambuf>
# include <type_traits>
namespace ed = ax::NodeEditor::Detail;
//------------------------------------------------------------------------------
//
// Animation
//
//------------------------------------------------------------------------------
ed::Animation::Animation(EditorContext* editor):
Editor(editor),
m_State(Stopped),
m_Time(0.0f),
m_Duration(0.0f)
{
}
ed::Animation::~Animation()
{
Stop();
}
void ed::Animation::Play(float duration)
{
if (IsPlaying())
Stop();
m_State = Playing;
if (duration < 0)
duration = 0.0f;
m_Time = 0.0f;
m_Duration = duration;
OnPlay();
Editor->RegisterAnimation(this);
if (duration == 0.0f)
Finish();
}
void ed::Animation::Stop()
{
if (!IsPlaying())
return;
m_State = Stopped;
Editor->UnregisterAnimation(this);
OnStop();
}
void ed::Animation::Finish()
{
if (!IsPlaying())
return;
OnFinish();
Stop();
}
void ed::Animation::Update()
{
if (!IsPlaying())
return;
m_Time += ImMax(0.0f, ImGui::GetIO().DeltaTime);
if (m_Time < m_Duration)
{
const float progress = GetProgress();
OnUpdate(progress);
}
else
{
OnFinish();
Stop();
}
}
//------------------------------------------------------------------------------
//
// Navigate Animation
//
//------------------------------------------------------------------------------
ed::NavigateAnimation::NavigateAnimation(EditorContext* editor, NavigateAction& scrollAction):
Animation(editor),
Action(scrollAction)
{
}
void ed::NavigateAnimation::NavigateTo(const ImRect& target, float duration)
{
Stop();
m_Start = Action.GetViewRect();
m_Target = target;
// Skip tiny animations
auto minoffset = m_Target.Min - m_Start.Min;
auto maxOffset = m_Target.Max - m_Start.Max;
auto epsilon = 1e-4f;
if (ImFabs(minoffset.x) < epsilon && ImFabs(minoffset.y) < epsilon &&
ImFabs(maxOffset.x) < epsilon && ImFabs(maxOffset.y) < epsilon)
{
duration = 0;
}
Play(duration);
}
void ed::NavigateAnimation::OnUpdate(float progress)
{
ImRect current;
current.Min = ImEasing::EaseOutQuad(m_Start.Min, m_Target.Min - m_Start.Min, progress);
current.Max = ImEasing::EaseOutQuad(m_Start.Max, m_Target.Max - m_Start.Max, progress);
Action.SetViewRect(current);
}
void ed::NavigateAnimation::OnStop()
{
Editor->MakeDirty(SaveReasonFlags::Navigation);
}
void ed::NavigateAnimation::OnFinish()
{
Action.SetViewRect(m_Target);
Editor->MakeDirty(SaveReasonFlags::Navigation);
}
//------------------------------------------------------------------------------
//
// Flow Animation
//
//------------------------------------------------------------------------------
ed::FlowAnimation::FlowAnimation(FlowAnimationController* controller):
Animation(controller->Editor),
Controller(controller),
m_Link(nullptr),
m_Offset(0.0f),
m_PathLength(0.0f)
{
}
void ed::FlowAnimation::Flow(ed::Link* link, float markerDistance, float speed, float duration)
{
Stop();
if (m_Link != link)
{
m_Offset = 0.0f;
ClearPath();
}
if (m_MarkerDistance != markerDistance)
ClearPath();
m_MarkerDistance = markerDistance;
m_Speed = speed;
m_Link = link;
Play(duration);
}
void ed::FlowAnimation::Draw(ImDrawList* drawList)
{
if (!IsPlaying() || !IsLinkValid() || !m_Link->IsVisible())
return;
if (!IsPathValid())
UpdatePath();
m_Offset = fmodf(m_Offset, m_MarkerDistance);
if (m_Offset < 0)
m_Offset += m_MarkerDistance;
const auto progress = GetProgress();
const auto flowAlpha = 1.0f - progress * progress;
const auto flowColor = Editor->GetColor(StyleColor_Flow, flowAlpha);
//const auto flowPath = Link->GetCurve();
m_Link->Draw(drawList, flowColor, 2.0f);
if (IsPathValid())
{
//Offset = 0;
const auto markerAlpha = powf(1.0f - progress, 0.35f);
const auto markerRadius = 4.0f * (1.0f - progress) + 2.0f;
const auto markerColor = Editor->GetColor(StyleColor_FlowMarker, markerAlpha);
for (float d = m_Offset; d < m_PathLength; d += m_MarkerDistance)
drawList->AddCircleFilled(SamplePath(d), markerRadius, markerColor);
}
}
bool ed::FlowAnimation::IsLinkValid() const
{
return m_Link && m_Link->m_IsLive;
}
bool ed::FlowAnimation::IsPathValid() const
{
return m_Path.size() > 1 && m_PathLength > 0.0f && m_Link->m_Start == m_LastStart && m_Link->m_End == m_LastEnd;
}
void ed::FlowAnimation::UpdatePath()
{
if (!IsLinkValid())
{
ClearPath();
return;
}
const auto curve = m_Link->GetCurve();
m_LastStart = m_Link->m_Start;
m_LastEnd = m_Link->m_End;
m_PathLength = ImCubicBezierLength(curve.P0, curve.P1, curve.P2, curve.P3);
auto collectPointsCallback = [this](ImCubicBezierFixedStepSample& result)
{
m_Path.push_back(CurvePoint{ result.Length, result.Point });
};
const auto step = ImMax(m_MarkerDistance * 0.5f, 15.0f);
m_Path.resize(0);
ImCubicBezierFixedStep(collectPointsCallback, curve, step, false, 0.5f, 0.001f);
}
void ed::FlowAnimation::ClearPath()
{
vector<CurvePoint>().swap(m_Path);
m_PathLength = 0.0f;
}
ImVec2 ed::FlowAnimation::SamplePath(float distance) const
{
//distance = ImMax(0.0f, std::min(distance, PathLength));
auto endPointIt = std::find_if(m_Path.begin(), m_Path.end(), [distance](const CurvePoint& p) { return distance < p.Distance; });
if (endPointIt == m_Path.end())
endPointIt = m_Path.end() - 1;
else if (endPointIt == m_Path.begin())
endPointIt = m_Path.begin() + 1;
const auto& start = endPointIt[-1];
const auto& end = *endPointIt;
const auto t = (distance - start.Distance) / (end.Distance - start.Distance);
return start.Point + (end.Point - start.Point) * t;
}
void ed::FlowAnimation::OnUpdate(float progress)
{
IM_UNUSED(progress);
m_Offset += m_Speed * ImGui::GetIO().DeltaTime;
}
void ed::FlowAnimation::OnStop()
{
Controller->Release(this);
}
//------------------------------------------------------------------------------
//
// Flow Animation Controller
//
//------------------------------------------------------------------------------
ed::FlowAnimationController::FlowAnimationController(EditorContext* editor):
AnimationController(editor)
{
}
ed::FlowAnimationController::~FlowAnimationController()
{
for (auto animation : m_Animations)
delete animation;
}
void ed::FlowAnimationController::Flow(Link* link, FlowDirection direction)
{
if (!link || !link->m_IsLive)
return;
auto& editorStyle = GetStyle();
auto animation = GetOrCreate(link);
float speedDirection = 1.0f;
if (direction == FlowDirection::Backward)
speedDirection = -1.0f;
animation->Flow(link, editorStyle.FlowMarkerDistance, editorStyle.FlowSpeed * speedDirection, editorStyle.FlowDuration);
}
void ed::FlowAnimationController::Draw(ImDrawList* drawList)
{
if (m_Animations.empty())
return;
drawList->ChannelsSetCurrent(c_LinkChannel_Flow);
for (auto animation : m_Animations)
animation->Draw(drawList);
}
ed::FlowAnimation* ed::FlowAnimationController::GetOrCreate(Link* link)
{
// Return live animation which match target link
{
auto animationIt = std::find_if(m_Animations.begin(), m_Animations.end(), [link](FlowAnimation* animation) { return animation->m_Link == link; });
if (animationIt != m_Animations.end())
return *animationIt;
}
// There are no live animations for target link, try to reuse inactive old one
if (!m_FreePool.empty())
{
auto animation = m_FreePool.back();
m_FreePool.pop_back();
return animation;
}
// Cache miss, allocate new one
auto animation = new FlowAnimation(this);
m_Animations.push_back(animation);
return animation;
}
void ed::FlowAnimationController::Release(FlowAnimation* animation)
{
IM_UNUSED(animation);
}