| #include "rive/shapes/paint/trim_path.hpp" | |
| #include "rive/shapes/paint/stroke.hpp" | |
| #include "rive/factory.hpp" | |
| using namespace rive; | |
| StatusCode TrimPath::onAddedClean(CoreContext* context) | |
| { | |
| if (!parent()->is<Stroke>()) | |
| { | |
| return StatusCode::InvalidObject; | |
| } | |
| parent()->as<Stroke>()->addStrokeEffect(this); | |
| return StatusCode::Ok; | |
| } | |
| void TrimPath::trimRawPath(const RawPath& source) | |
| { | |
| m_rawPath.rewind(); | |
| auto renderOffset = std::fmod(std::fmod(offset(), 1.0f) + 1.0f, 1.0f); | |
| // Build up contours if they're empty. | |
| if (m_contours.empty()) | |
| { | |
| ContourMeasureIter iter(&source); | |
| while (auto meas = iter.next()) | |
| { | |
| m_contours.push_back(meas); | |
| } | |
| } | |
| switch (mode()) | |
| { | |
| case TrimPathMode::sequential: | |
| { | |
| float totalLength = 0.0f; | |
| for (auto contour : m_contours) | |
| { | |
| totalLength += contour->length(); | |
| } | |
| auto startLength = totalLength * (start() + renderOffset); | |
| auto endLength = totalLength * (end() + renderOffset); | |
| if (endLength < startLength) | |
| { | |
| float swap = startLength; | |
| startLength = endLength; | |
| endLength = swap; | |
| } | |
| if (startLength > totalLength) | |
| { | |
| startLength -= totalLength; | |
| endLength -= totalLength; | |
| } | |
| int i = 0, subPathCount = (int)m_contours.size(); | |
| while (endLength > 0) | |
| { | |
| auto contour = m_contours[i % subPathCount]; | |
| auto contourLength = contour->length(); | |
| if (startLength < contourLength) | |
| { | |
| contour->getSegment(startLength, endLength, &m_rawPath, true); | |
| endLength -= contourLength; | |
| startLength = 0; | |
| } | |
| else | |
| { | |
| startLength -= contourLength; | |
| endLength -= contourLength; | |
| } | |
| i++; | |
| } | |
| } | |
| break; | |
| case TrimPathMode::synchronized: | |
| { | |
| for (auto contour : m_contours) | |
| { | |
| auto contourLength = contour->length(); | |
| auto startLength = contourLength * (start() + renderOffset); | |
| auto endLength = contourLength * (end() + renderOffset); | |
| if (endLength < startLength) | |
| { | |
| auto length = startLength; | |
| startLength = endLength; | |
| endLength = length; | |
| } | |
| if (startLength > contourLength) | |
| { | |
| startLength -= contourLength; | |
| endLength -= contourLength; | |
| } | |
| contour->getSegment(startLength, endLength, &m_rawPath, true); | |
| while (endLength > contourLength) | |
| { | |
| startLength = 0; | |
| endLength -= contourLength; | |
| contour->getSegment(startLength, endLength, &m_rawPath, true); | |
| } | |
| } | |
| } | |
| break; | |
| default: | |
| RIVE_UNREACHABLE(); | |
| } | |
| } | |
| RenderPath* TrimPath::effectPath(const RawPath& source, Factory* factory) | |
| { | |
| if (m_renderPath != nullptr) | |
| { | |
| // Previous result hasn't been invalidated, it's still good. | |
| return m_renderPath; | |
| } | |
| trimRawPath(source); | |
| if (!m_trimmedPath) | |
| { | |
| m_trimmedPath = factory->makeEmptyRenderPath(); | |
| } | |
| else | |
| { | |
| m_trimmedPath->rewind(); | |
| } | |
| m_renderPath = m_trimmedPath.get(); | |
| m_rawPath.addTo(m_renderPath); | |
| return m_renderPath; | |
| } | |
| void TrimPath::invalidateEffect() | |
| { | |
| invalidateTrim(); | |
| // This is usually sent when the path is changed so we need to also | |
| // invalidate the contours, not just the trim effect. | |
| m_contours.clear(); | |
| } | |
| void TrimPath::invalidateTrim() | |
| { | |
| m_renderPath = nullptr; | |
| if (parent() != nullptr) | |
| { | |
| auto stroke = parent()->as<Stroke>(); | |
| stroke->parent()->addDirt(ComponentDirt::Paint); | |
| stroke->invalidateRendering(); | |
| } | |
| } | |
| void TrimPath::startChanged() { invalidateTrim(); } | |
| void TrimPath::endChanged() { invalidateTrim(); } | |
| void TrimPath::offsetChanged() { invalidateTrim(); } | |
| void TrimPath::modeValueChanged() { invalidateTrim(); } | |
| StatusCode TrimPath::onAddedDirty(CoreContext* context) | |
| { | |
| auto code = Super::onAddedDirty(context); | |
| if (code != StatusCode::Ok) | |
| { | |
| return code; | |
| } | |
| switch (mode()) | |
| { | |
| case TrimPathMode::sequential: | |
| case TrimPathMode::synchronized: | |
| return StatusCode::Ok; | |
| } | |
| return StatusCode::InvalidObject; | |
| } |