blob: c2b72233e12853c51207436f14852b76b663ca43 [file] [log] [blame]
#include "rive/shapes/path_composer.hpp"
#include "rive/artboard.hpp"
#include "rive/renderer.hpp"
#include "rive/shapes/path.hpp"
#include "rive/shapes/shape.hpp"
#include "rive/factory.hpp"
using namespace rive;
PathComposer::PathComposer(Shape* shape) :
m_shape(shape), m_deferredPathDirt(false)
{}
void PathComposer::buildDependencies()
{
assert(m_shape != nullptr);
m_shape->addDependent(this);
for (auto path : m_shape->paths())
{
path->addDependent(this);
}
}
void PathComposer::onDirty(ComponentDirt dirt)
{
if (m_deferredPathDirt)
{
// We'd deferred the update, let's make sure the rest of our
// dependencies update too. Constraints need to update too, stroke
// effects, etc.
m_shape->pathChanged();
}
}
void PathComposer::update(ComponentDirt value)
{
if (hasDirt(value, ComponentDirt::Path | ComponentDirt::NSlicer))
{
if (m_shape->canDeferPathUpdate())
{
m_deferredPathDirt = true;
return;
}
m_deferredPathDirt = false;
if (m_shape->isFlagged(PathFlags::local))
{
if (m_localPath == nullptr)
{
m_localPath = artboard()->factory()->makeEmptyRenderPath();
}
else
{
m_localPath->rewind();
m_localRawPath.rewind();
}
auto world = m_shape->worldTransform();
Mat2D inverseWorld = world.invertOrIdentity();
// Get all the paths into local shape space.
for (auto path : m_shape->paths())
{
if (!path->isHidden() && !path->isCollapsed())
{
const auto localTransform =
inverseWorld * path->pathTransform();
m_localRawPath.addPath(path->rawPath(), &localTransform);
}
}
// TODO: add a CommandPath::copy(RawPath)
m_localRawPath.addTo(m_localPath.get());
}
if (m_shape->isFlagged(PathFlags::world))
{
if (m_worldPath == nullptr)
{
m_worldPath = artboard()->factory()->makeEmptyRenderPath();
}
else
{
m_worldPath->rewind();
m_worldRawPath.rewind();
}
for (auto path : m_shape->paths())
{
if (!path->isHidden() && !path->isCollapsed())
{
const Mat2D& transform = path->pathTransform();
m_worldRawPath.addPath(path->rawPath(), &transform);
}
}
// TODO: add a CommandPath::copy(RawPath)
m_worldRawPath.addTo(m_worldPath.get());
}
m_shape->markBoundsDirty();
}
}
// Instead of adding dirt and rely on the recursive behavior of the addDirt
// method, we need to explicitly add dirt to the dependents. The reason is that
// a collapsed shape will not clear its dirty path flag in the current frame
// since it is collapsed. So in a future frame if it is uncollapsed, we mark its
// path flag as dirty again, but since it was already dirty, the recursive part
// will not kick in and the dependents won't update. This scenario is not
// common, but it can happen when a solo toggles between an empty group and a
// path for example.
void PathComposer::pathCollapseChanged()
{
addDirt(ComponentDirt::Path);
for (auto d : dependents())
{
d->addDirt(ComponentDirt::Path, true);
}
}