| #include "rive/shapes/path_composer.hpp" | |
| #include "rive/artboard.hpp" | |
| #include "rive/renderer.hpp" | |
| #include "rive/shapes/path.hpp" | |
| #include "rive/shapes/shape.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)) | |
| { | |
| if (m_Shape->canDeferPathUpdate()) | |
| { | |
| m_deferredPathDirt = true; | |
| return; | |
| } | |
| m_deferredPathDirt = false; | |
| auto space = m_Shape->pathSpace(); | |
| bool hasConstraint = (space & PathSpace::FollowPath) == PathSpace::FollowPath; | |
| if ((space & PathSpace::Local) == PathSpace::Local) | |
| { | |
| if (m_LocalPath == nullptr) | |
| { | |
| PathSpace localSpace = | |
| (hasConstraint) ? PathSpace::Local & PathSpace::FollowPath : PathSpace::Local; | |
| m_LocalPath = m_Shape->makeCommandPath(localSpace); | |
| } | |
| else | |
| { | |
| m_LocalPath->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_LocalPath->addPath(path->commandPath(), localTransform); | |
| } | |
| } | |
| } | |
| if ((space & PathSpace::World) == PathSpace::World) | |
| { | |
| if (m_WorldPath == nullptr) | |
| { | |
| PathSpace worldSpace = | |
| (hasConstraint) ? PathSpace::World & PathSpace::FollowPath : PathSpace::World; | |
| m_WorldPath = m_Shape->makeCommandPath(worldSpace); | |
| } | |
| else | |
| { | |
| m_WorldPath->rewind(); | |
| } | |
| for (auto path : m_Shape->paths()) | |
| { | |
| if (!path->isHidden() && !path->isCollapsed()) | |
| { | |
| const Mat2D& transform = path->pathTransform(); | |
| m_WorldPath->addPath(path->commandPath(), transform); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // 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); | |
| } | |
| } |