| #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; |
| } |