blob: bd75f49dc4817a5aab451d16942f7a0f842fb53e [file] [log] [blame] [edit]
#include "rive/nested_artboard.hpp"
#include "rive/artboard.hpp"
#include "rive/backboard.hpp"
#include "rive/file.hpp"
#include "rive/importers/import_stack.hpp"
#include "rive/importers/backboard_importer.hpp"
#include "rive/nested_animation.hpp"
#include "rive/animation/nested_state_machine.hpp"
#include "rive/clip_result.hpp"
#include <limits>
#include <cassert>
using namespace rive;
NestedArtboard::NestedArtboard() {}
NestedArtboard::~NestedArtboard() {}
Core* NestedArtboard::clone() const
{
NestedArtboard* nestedArtboard =
static_cast<NestedArtboard*>(NestedArtboardBase::clone());
nestedArtboard->file(file());
if (m_Artboard == nullptr)
{
return nestedArtboard;
}
auto ni = m_Artboard->instance();
nestedArtboard->nest(ni.release());
return nestedArtboard;
}
void NestedArtboard::nest(Artboard* artboard)
{
assert(artboard != nullptr);
m_Artboard = artboard;
if (!m_Artboard->isInstance())
{
// We're just marking the source artboard so we can later instance from
// it. No need to advance it or change any of its properties.
// E.g. at import time, we return here.
return;
}
m_Artboard->frameOrigin(false);
m_Artboard->opacity(renderOpacity());
m_Artboard->volume(artboard->volume());
m_Instance = nullptr;
if (artboard->isInstance())
{
m_Instance.reset(
static_cast<ArtboardInstance*>(artboard)); // take ownership
}
// This allows for swapping after initial load (after onAddedClean has
// already been called).
m_Artboard->host(this);
}
Artboard* NestedArtboard::findArtboard(
ViewModelInstanceArtboard* viewModelInstanceArtboard)
{
if (viewModelInstanceArtboard == nullptr)
{
return nullptr;
}
if (viewModelInstanceArtboard->asset() != nullptr)
{
if (!parentArtboard()->isAncestor(
viewModelInstanceArtboard->asset()->artboard()))
{
return viewModelInstanceArtboard->asset()->artboard();
}
return nullptr;
}
else if (m_file != nullptr)
{
auto asset =
m_file->artboard(viewModelInstanceArtboard->propertyValue());
if (asset != nullptr)
{
auto artboardAsset = asset->as<Artboard>();
if (!parentArtboard()->isAncestor(artboardAsset))
{
return artboardAsset;
}
}
}
return nullptr;
}
void NestedArtboard::clearNestedAnimations()
{
for (auto& animation : m_NestedAnimations)
{
// Release the nested animation dependencies. The file will take care of
// destroying the nested animation itself.
animation->releaseDependencies();
}
m_NestedAnimations.clear();
}
void NestedArtboard::updateArtboard(
ViewModelInstanceArtboard* viewModelInstanceArtboard)
{
clearDataContext();
clearNestedAnimations();
m_boundNestedStateMachine = nullptr;
// If asset == nullptr and propertyValue == -1, it means that the user
// explicitly set the asset to null, so only in that case we clear the
// artboard
if (viewModelInstanceArtboard != nullptr &&
viewModelInstanceArtboard->asset() == nullptr &&
viewModelInstanceArtboard->propertyValue() == -1)
{
if (m_Artboard)
{
m_Artboard->host(nullptr);
m_Artboard = nullptr;
}
m_Instance = nullptr;
return;
}
Artboard* artboard = findArtboard(viewModelInstanceArtboard);
if (artboard != nullptr)
{
auto artboardInstance = artboard->instance();
if (artboard->stateMachineCount() > 0)
{
auto nestedStateMachine = new NestedStateMachine();
nestedStateMachine->animationId(0);
nestedStateMachine->initializeAnimation(artboardInstance.get());
addNestedAnimation(nestedStateMachine);
m_boundNestedStateMachine.reset(static_cast<NestedStateMachine*>(
nestedStateMachine)); // take ownership
}
nest(artboardInstance.release());
if (m_dataContext != nullptr && m_viewModelInstance == nullptr)
{
internalDataContext(m_dataContext);
}
else if (m_viewModelInstance != nullptr)
{
bindViewModelInstance(m_viewModelInstance, m_dataContext);
}
// TODO: @hernan review what dirt to add
addDirt(ComponentDirt::Filthy);
}
}
static Mat2D makeTranslate(const Artboard* artboard)
{
return Mat2D::fromTranslate(-artboard->originX() * artboard->width(),
-artboard->originY() * artboard->height());
}
void NestedArtboard::draw(Renderer* renderer)
{
if (m_Artboard == nullptr)
{
return;
}
ClipResult clipResult = applyClip(renderer);
if (clipResult == ClipResult::noClip)
{
// We didn't clip, so make sure to save as we'll be doing some
// transformations.
renderer->save();
}
if (clipResult != ClipResult::emptyClip)
{
renderer->transform(worldTransform());
m_Artboard->draw(renderer);
}
renderer->restore();
}
Core* NestedArtboard::hitTest(HitInfo* hinfo, const Mat2D& xform)
{
if (m_Artboard == nullptr)
{
return nullptr;
}
hinfo->mounts.push_back(this);
auto mx = xform * worldTransform() * makeTranslate(m_Artboard);
if (auto c = m_Artboard->hitTest(hinfo, mx))
{
return c;
}
hinfo->mounts.pop_back();
return nullptr;
}
bool NestedArtboard::hitTestHost(const Vec2D& position,
bool skipOnUnclipped,
ArtboardInstance* artboard)
{
return parent()->hitTestPoint(worldTransform() * position,
skipOnUnclipped,
false);
}
Vec2D NestedArtboard::hostTransformPoint(const Vec2D& vec,
ArtboardInstance* artboardInstance)
{
auto localVec = Vec2D::transformMat2D(vec, worldTransform());
auto ab = artboard();
return ab ? ab->rootTransform(localVec) : localVec;
}
StatusCode NestedArtboard::import(ImportStack& importStack)
{
auto backboardImporter =
importStack.latest<BackboardImporter>(Backboard::typeKey);
if (backboardImporter == nullptr)
{
return StatusCode::MissingObject;
}
backboardImporter->addNestedArtboard(this);
return Super::import(importStack);
}
void NestedArtboard::addNestedAnimation(NestedAnimation* nestedAnimation)
{
m_NestedAnimations.push_back(nestedAnimation);
}
StatusCode NestedArtboard::onAddedClean(CoreContext* context)
{
// N.B. The nested instance will be null here for the source artboards.
// Instances will have a nestedInstance available. This is a good thing as
// it ensures that we only instance animations in artboard instances. It
// does require that we always use an artboard instance (not just the source
// artboard) when working with nested artboards, but in general this is good
// practice for any loaded Rive file.
assert(m_Artboard == nullptr || m_Artboard == m_Instance.get());
if (m_Instance)
{
for (auto animation : m_NestedAnimations)
{
animation->initializeAnimation(m_Instance.get());
}
m_Artboard->host(this);
}
return Super::onAddedClean(context);
}
void NestedArtboard::update(ComponentDirt value)
{
Super::update(value);
if (m_Artboard == nullptr)
{
return;
}
if (hasDirt(value, ComponentDirt::RenderOpacity))
{
m_Artboard->opacity(renderOpacity());
}
if (hasDirt(value, ComponentDirt::Components))
{
// We intentionally discard whether or not this updated because by the
// end of the pass all the dirt is removed and only another advance of
// animations/statemachines can re-add it.
m_Artboard->updatePass(false);
}
}
bool NestedArtboard::hasNestedStateMachines() const
{
for (auto animation : m_NestedAnimations)
{
if (animation->is<NestedStateMachine>())
{
return true;
}
}
return false;
}
Span<NestedAnimation*> NestedArtboard::nestedAnimations()
{
return m_NestedAnimations;
}
NestedArtboard* NestedArtboard::nestedArtboard(std::string name) const
{
if (m_Instance != nullptr)
{
return m_Instance->nestedArtboard(name);
}
return nullptr;
}
NestedStateMachine* NestedArtboard::stateMachine(std::string name) const
{
for (auto animation : m_NestedAnimations)
{
if (animation->is<NestedStateMachine>() && animation->name() == name)
{
return animation->as<NestedStateMachine>();
}
}
return nullptr;
}
NestedInput* NestedArtboard::input(std::string name) const
{
return input(name, "");
}
NestedInput* NestedArtboard::input(std::string name,
std::string stateMachineName) const
{
if (!stateMachineName.empty())
{
auto nestedSM = stateMachine(stateMachineName);
if (nestedSM != nullptr)
{
return nestedSM->input(name);
}
}
else
{
for (auto animation : m_NestedAnimations)
{
if (animation->is<NestedStateMachine>())
{
auto input = animation->as<NestedStateMachine>()->input(name);
if (input != nullptr)
{
return input;
}
}
}
}
return nullptr;
}
bool NestedArtboard::worldToLocal(Vec2D world, Vec2D* local)
{
assert(local != nullptr);
if (m_Artboard == nullptr)
{
return false;
}
Mat2D toMountedArtboard;
if (!worldTransform().invert(&toMountedArtboard))
{
return false;
}
*local = toMountedArtboard * world;
return true;
}
Vec2D NestedArtboard::measureLayout(float width,
LayoutMeasureMode widthMode,
float height,
LayoutMeasureMode heightMode)
{
return Vec2D(std::min(widthMode == LayoutMeasureMode::undefined
? std::numeric_limits<float>::max()
: width,
m_Instance ? m_Instance->width() : 0.0f),
std::min(heightMode == LayoutMeasureMode::undefined
? std::numeric_limits<float>::max()
: height,
m_Instance ? m_Instance->height() : 0.0f));
}
void NestedArtboard::controlSize(Vec2D size,
LayoutScaleType widthScaleType,
LayoutScaleType heightScaleType,
LayoutDirection direction)
{}
void NestedArtboard::decodeDataBindPathIds(Span<const uint8_t> value)
{
BinaryReader reader(value);
while (!reader.reachedEnd())
{
auto val = reader.readVarUintAs<uint32_t>();
m_DataBindPathIdsBuffer.push_back(val);
}
}
void NestedArtboard::copyDataBindPathIds(const NestedArtboardBase& object)
{
m_DataBindPathIdsBuffer =
object.as<NestedArtboard>()->m_DataBindPathIdsBuffer;
}
void NestedArtboard::internalDataContext(DataContext* value)
{
m_dataContext = value;
m_viewModelInstance = nullptr;
if (artboardInstance() != nullptr)
{
artboardInstance()->internalDataContext(value);
for (auto& animation : m_NestedAnimations)
{
if (animation->is<NestedStateMachine>())
{
animation->as<NestedStateMachine>()->dataContext(value);
}
}
}
}
void NestedArtboard::clearDataContext()
{
if (artboardInstance() != nullptr)
{
artboardInstance()->clearDataContext();
for (auto& animation : m_NestedAnimations)
{
if (animation->is<NestedStateMachine>())
{
animation->as<NestedStateMachine>()->clearDataContext();
}
}
}
}
void NestedArtboard::unbind()
{
if (artboardInstance() != nullptr)
{
artboardInstance()->unbind();
}
}
void NestedArtboard::updateDataBinds()
{
if (artboardInstance() != nullptr)
{
artboardInstance()->updateDataBinds();
}
}
void NestedArtboard::bindViewModelInstance(
rcp<ViewModelInstance> viewModelInstance,
DataContext* parent)
{
m_dataContext = parent;
m_viewModelInstance = viewModelInstance;
if (artboardInstance() != nullptr)
{
artboardInstance()->bindViewModelInstance(viewModelInstance, parent);
for (auto& animation : m_NestedAnimations)
{
if (animation->is<NestedStateMachine>())
{
animation->as<NestedStateMachine>()->dataContext(
artboardInstance()->dataContext());
}
}
}
}
bool NestedArtboard::advanceComponent(float elapsedSeconds, AdvanceFlags flags)
{
if (m_Artboard == nullptr || isCollapsed())
{
return false;
}
bool keepGoing = false;
bool advanceNested =
(flags & AdvanceFlags::AdvanceNested) == AdvanceFlags::AdvanceNested;
if (advanceNested)
{
bool newFrame =
(flags & AdvanceFlags::NewFrame) == AdvanceFlags::NewFrame;
for (auto animation : m_NestedAnimations)
{
// If it is not a new frame, we only advance state machines. And we
// first validate whether their state has changed. Then and only
// then we advance the state machine. This avoids triggering dirt
// from advances that make intermediate value changes but finally
// settle in the same value
if (!newFrame)
{
if (animation->is<NestedStateMachine>())
{
if (animation->as<NestedStateMachine>()->tryChangeState())
{
if (animation->advance(elapsedSeconds, newFrame))
{
keepGoing = true;
}
}
}
}
else
{
if (animation->advance(elapsedSeconds, newFrame))
{
keepGoing = true;
}
}
}
}
auto advancingFlags = flags & ~AdvanceFlags::IsRoot;
if (m_Artboard->advanceInternal(elapsedSeconds, advancingFlags))
{
keepGoing = true;
}
if (m_Artboard->hasDirt(ComponentDirt::Components))
{
// The animation(s) caused the artboard to need an update.
addDirt(ComponentDirt::Components);
}
return keepGoing;
}
void NestedArtboard::reset()
{
if (m_Artboard)
{
m_Artboard->reset();
}
}
void NestedArtboard::file(File* value) { m_file = value; }
File* NestedArtboard::file() const { return m_file; }