blob: a882dac2ab2190c84eea29637abecd5609197b26 [file] [log] [blame]
#include "rive/artboard.hpp"
#include "rive/backboard.hpp"
#include "rive/animation/animation.hpp"
#include "rive/dependency_sorter.hpp"
#include "rive/draw_rules.hpp"
#include "rive/draw_target.hpp"
#include "rive/draw_target_placement.hpp"
#include "rive/drawable.hpp"
#include "rive/node.hpp"
#include "rive/renderer.hpp"
#include "rive/shapes/paint/shape_paint.hpp"
#include "rive/importers/import_stack.hpp"
#include "rive/importers/backboard_importer.hpp"
#include "rive/nested_artboard.hpp"
#include <unordered_map>
using namespace rive;
Artboard::~Artboard()
{
for (auto object : m_Objects)
{
// First object is artboard
if (object == this)
{
continue;
}
delete object;
}
// Instances reference back to the original artboard's animations and state
// machines, so don't delete them here, they'll get cleaned up when the
// source is deleted.
if (!m_IsInstance)
{
for (auto object : m_Animations)
{
delete object;
}
for (auto object : m_StateMachines)
{
delete object;
}
}
delete m_ClipPath;
delete m_BackgroundPath;
}
static bool canContinue(StatusCode code)
{
// We currently only cease loading on invalid object.
return code != StatusCode::InvalidObject;
}
StatusCode Artboard::initialize()
{
StatusCode code;
m_BackgroundPath = makeCommandPath(PathSpace::Neither);
m_ClipPath = makeCommandPath(PathSpace::Neither);
// onAddedDirty guarantees that all objects are now available so they can be
// looked up by index/id. This is where nodes find their parents, but they
// can't assume that their parent's parent will have resolved yet.
for (auto object : m_Objects)
{
if (object == nullptr)
{
// objects can be null if they were not understood by this runtime.
continue;
}
if (!canContinue(code = object->onAddedDirty(this)))
{
return code;
}
}
for (auto object : m_Animations)
{
if (!canContinue(code = object->onAddedDirty(this)))
{
return code;
}
}
for (auto object : m_StateMachines)
{
if (!canContinue(code = object->onAddedDirty(this)))
{
return code;
}
}
// Store a map of the drawRules to make it easier to lookup the matching
// rule for a transform component.
std::unordered_map<Core*, DrawRules*> componentDrawRules;
// onAddedClean is called when all individually referenced components have
// been found and so components can look at other components' references and
// assume that they have resolved too. This is where the whole hierarchy is
// linked up and we can traverse it to find other references (my parent's
// parent should be type X can be checked now).
for (auto object : m_Objects)
{
if (object == nullptr)
{
continue;
}
if (!canContinue(code = object->onAddedClean(this)))
{
return code;
}
switch (object->coreType())
{
case DrawRulesBase::typeKey:
{
DrawRules* rules = reinterpret_cast<DrawRules*>(object);
Core* component = resolve(rules->parentId());
if (component != nullptr)
{
componentDrawRules[component] = rules;
}
else
{
fprintf(stderr,
"Artboard::initialize - Draw rule targets missing "
"component width id %d\n",
rules->parentId());
}
break;
}
case NestedArtboardBase::typeKey:
m_NestedArtboards.push_back(object->as<NestedArtboard>());
break;
}
}
for (auto object : m_Animations)
{
if (!canContinue(code = object->onAddedClean(this)))
{
return code;
}
}
for (auto object : m_StateMachines)
{
if (!canContinue(code = object->onAddedClean(this)))
{
return code;
}
}
// Multi-level references have been built up, now we can
// actually mark what's dependent on what.
for (auto object : m_Objects)
{
if (object == nullptr)
{
continue;
}
if (object->is<Component>())
{
object->as<Component>()->buildDependencies();
}
if (object->is<Drawable>())
{
Drawable* drawable = object->as<Drawable>();
m_Drawables.push_back(drawable);
for (ContainerComponent* parent = drawable; parent != nullptr;
parent = parent->parent())
{
auto itr = componentDrawRules.find(parent);
if (itr != componentDrawRules.end())
{
drawable->flattenedDrawRules = itr->second;
break;
}
}
}
}
sortDependencies();
DrawTarget root;
// Build up the draw order. Look for draw targets and build
// their dependencies.
for (auto object : m_Objects)
{
if (object == nullptr)
{
continue;
}
if (object->is<DrawTarget>())
{
DrawTarget* target = object->as<DrawTarget>();
root.addDependent(target);
auto dependentRules = target->drawable()->flattenedDrawRules;
if (dependentRules != nullptr)
{
// Because we don't store targets on rules, we need
// to find the targets that belong to this rule
// here.
for (auto object : m_Objects)
{
if (object != nullptr && object->is<DrawTarget>())
{
DrawTarget* dependentTarget = object->as<DrawTarget>();
if (dependentTarget->parent() == dependentRules)
{
dependentTarget->addDependent(target);
}
}
}
}
}
}
DependencySorter sorter;
std::vector<Component*> drawTargetOrder;
sorter.sort(&root, drawTargetOrder);
auto itr = drawTargetOrder.begin();
itr++;
while (itr != drawTargetOrder.end())
{
m_DrawTargets.push_back(reinterpret_cast<DrawTarget*>(*itr++));
}
return StatusCode::Ok;
}
void Artboard::sortDrawOrder()
{
for (auto target : m_DrawTargets)
{
target->first = target->last = nullptr;
}
m_FirstDrawable = nullptr;
Drawable* lastDrawable = nullptr;
for (auto drawable : m_Drawables)
{
auto rules = drawable->flattenedDrawRules;
if (rules != nullptr && rules->activeTarget() != nullptr)
{
auto target = rules->activeTarget();
if (target->first == nullptr)
{
target->first = target->last = drawable;
drawable->prev = drawable->next = nullptr;
}
else
{
target->last->next = drawable;
drawable->prev = target->last;
target->last = drawable;
drawable->next = nullptr;
}
}
else
{
drawable->prev = lastDrawable;
drawable->next = nullptr;
if (lastDrawable == nullptr)
{
lastDrawable = m_FirstDrawable = drawable;
}
else
{
lastDrawable->next = drawable;
lastDrawable = drawable;
}
}
}
for (auto rule : m_DrawTargets)
{
if (rule->first == nullptr)
{
continue;
}
auto targetDrawable = rule->drawable();
switch (rule->placement())
{
case DrawTargetPlacement::before:
{
if (targetDrawable->prev != nullptr)
{
targetDrawable->prev->next = rule->first;
rule->first->prev = targetDrawable->prev;
}
if (targetDrawable == m_FirstDrawable)
{
m_FirstDrawable = rule->first;
}
targetDrawable->prev = rule->last;
rule->last->next = targetDrawable;
break;
}
case DrawTargetPlacement::after:
{
if (targetDrawable->next != nullptr)
{
targetDrawable->next->prev = rule->last;
rule->last->next = targetDrawable->next;
}
if (targetDrawable == lastDrawable)
{
lastDrawable = rule->last;
}
targetDrawable->next = rule->first;
rule->first->prev = targetDrawable;
break;
}
}
}
m_FirstDrawable = lastDrawable;
}
void Artboard::sortDependencies()
{
DependencySorter sorter;
sorter.sort(this, m_DependencyOrder);
unsigned int graphOrder = 0;
for (auto component : m_DependencyOrder)
{
component->m_GraphOrder = graphOrder++;
}
m_Dirt |= ComponentDirt::Components;
}
void Artboard::addObject(Core* object) { m_Objects.push_back(object); }
void Artboard::addAnimation(LinearAnimation* object)
{
m_Animations.push_back(object);
}
void Artboard::addStateMachine(StateMachine* object)
{
m_StateMachines.push_back(object);
}
void Artboard::addNestedArtboard(NestedArtboard* artboard)
{
m_NestedArtboards.push_back(artboard);
}
Core* Artboard::resolve(int id) const
{
if (id < 0 || id >= static_cast<int>(m_Objects.size()))
{
return nullptr;
}
return m_Objects[id];
}
void Artboard::onComponentDirty(Component* component)
{
m_Dirt |= ComponentDirt::Components;
/// If the order of the component is less than the current dirt
/// depth, update the dirt depth so that the update loop can break
/// out early and re-run (something up the tree is dirty).
if (component->graphOrder() < m_DirtDepth)
{
m_DirtDepth = component->graphOrder();
}
}
void Artboard::onDirty(ComponentDirt dirt)
{
m_Dirt |= ComponentDirt::Components;
}
void Artboard::update(ComponentDirt value)
{
if (hasDirt(value, ComponentDirt::DrawOrder))
{
sortDrawOrder();
}
if (hasDirt(value, ComponentDirt::Path))
{
m_ClipPath->reset();
if (m_FrameOrigin)
{
m_ClipPath->addRect(0.0f, 0.0f, width(), height());
}
else
{
m_ClipPath->addRect(
-width() * originX(), -height() * originY(), width(), height());
}
m_BackgroundPath->addRect(
-width() * originX(), -height() * originY(), width(), height());
}
}
bool Artboard::updateComponents()
{
if (hasDirt(ComponentDirt::Components))
{
const int maxSteps = 100;
int step = 0;
auto count = m_DependencyOrder.size();
while (hasDirt(ComponentDirt::Components) && step < maxSteps)
{
m_Dirt = m_Dirt & ~ComponentDirt::Components;
// Track dirt depth here so that if something else marks
// dirty, we restart.
for (unsigned int i = 0; i < count; i++)
{
auto component = m_DependencyOrder[i];
m_DirtDepth = i;
auto d = component->m_Dirt;
if (d == ComponentDirt::None)
{
continue;
}
component->m_Dirt = ComponentDirt::None;
component->update(d);
// If the update changed the dirt depth by adding dirt
// to something before us (in the DAG), early out and
// re-run the update.
if (m_DirtDepth < i)
{
// We put this in here just to know if we need to
// keep this around...
assert(false);
break;
}
}
step++;
}
return true;
}
return false;
}
bool Artboard::advance(double elapsedSeconds)
{
for (auto nestedArtboard : m_NestedArtboards)
{
nestedArtboard->advance(elapsedSeconds);
}
return updateComponents();
}
void Artboard::draw(Renderer* renderer)
{
renderer->save();
if (clip())
{
renderer->clipPath(m_ClipPath->renderPath());
}
if (m_FrameOrigin)
{
Mat2D artboardTransform;
artboardTransform[4] = width() * originX();
artboardTransform[5] = height() * originY();
renderer->transform(artboardTransform);
}
for (auto shapePaint : m_ShapePaints)
{
shapePaint->draw(renderer, m_BackgroundPath);
}
for (auto drawable = m_FirstDrawable; drawable != nullptr;
drawable = drawable->prev)
{
if (drawable->isHidden())
{
continue;
}
drawable->draw(renderer);
}
renderer->restore();
}
AABB Artboard::bounds() const { return AABB(0.0f, 0.0f, width(), height()); }
LinearAnimation* Artboard::firstAnimation() const
{
if (m_Animations.empty())
{
return nullptr;
}
return m_Animations.front();
}
LinearAnimation* Artboard::animation(std::string name) const
{
for (auto animation : m_Animations)
{
if (animation->name() == name)
{
return animation;
}
}
return nullptr;
}
LinearAnimation* Artboard::animation(size_t index) const
{
if (index >= m_Animations.size())
{
return nullptr;
}
return m_Animations[index];
}
StateMachine* Artboard::firstStateMachine() const
{
if (m_StateMachines.empty())
{
return nullptr;
}
return m_StateMachines.front();
}
StateMachine* Artboard::stateMachine(std::string name) const
{
for (auto machine : m_StateMachines)
{
if (machine->name() == name)
{
return machine;
}
}
return nullptr;
}
StateMachine* Artboard::stateMachine(size_t index) const
{
if (index >= m_StateMachines.size())
{
return nullptr;
}
return m_StateMachines[index];
}
Artboard* Artboard::instance() const
{
auto artboardClone = clone()->as<Artboard>();
artboardClone->m_FrameOrigin = m_FrameOrigin;
std::vector<Core*>& cloneObjects = artboardClone->m_Objects;
cloneObjects.push_back(artboardClone);
// Skip first object (artboard).
auto itr = m_Objects.begin();
while (++itr != m_Objects.end())
{
auto object = *itr;
cloneObjects.push_back(object == nullptr ? nullptr : object->clone());
}
for (auto animation : m_Animations)
{
artboardClone->m_Animations.push_back(animation);
}
for (auto stateMachine : m_StateMachines)
{
artboardClone->m_StateMachines.push_back(stateMachine);
}
if (artboardClone->initialize() != StatusCode::Ok)
{
delete artboardClone;
artboardClone = nullptr;
}
else
{
artboardClone->m_IsInstance = true;
}
return artboardClone;
}
void Artboard::frameOrigin(bool value)
{
if (value == m_FrameOrigin)
{
return;
}
m_FrameOrigin = value;
addDirt(ComponentDirt::Path);
}
StatusCode Artboard::import(ImportStack& importStack)
{
auto backboardImporter =
importStack.latest<BackboardImporter>(Backboard::typeKey);
if (backboardImporter == nullptr)
{
return StatusCode::MissingObject;
}
StatusCode result = Super::import(importStack);
if (result == StatusCode::Ok)
{
backboardImporter->addArtboard(this);
}
else
{
backboardImporter->addMissingArtboard();
}
return result;
}