blob: ffe744464a4b205a7f55b2497b409e02478b81d0 [file] [log] [blame] [edit]
#include "rive/shapes/paint/dash_path.hpp"
#include "rive/shapes/paint/dash.hpp"
#include "rive/shapes/paint/stroke.hpp"
#include "rive/factory.hpp"
using namespace rive;
void PathDasher::invalidateSourcePath()
{
m_contours.clear();
invalidateDash();
}
void PathDasher::invalidateDash() { m_path.rewind(); }
ShapePaintPath* PathDasher::dash(const RawPath* source,
Dash* offset,
Span<Dash*> dashes)
{
if (m_path.hasRenderPath())
{
// Previous result hasn't been invalidated, it's still good.
return &m_path;
}
m_path.rewind();
return applyDash(source, offset, dashes);
}
ShapePaintPath* PathDasher::applyDash(const RawPath* source,
Dash* offset,
Span<Dash*> dashes)
{
if (m_contours.empty())
{
// 0.5f / 8.0f is a value that seems to look good on dashes with small
// gaps and scaled
ContourMeasureIter iter(source, 0.0625f);
while (auto meas = iter.next())
{
m_contours.push_back(meas);
}
}
// Make sure dashes have some length.
bool hasValidDash = false;
for (const rcp<ContourMeasure>& contour : m_contours)
{
for (auto dash : dashes)
{
if (dash->normalizedLength(contour->length()) > 0.0f)
{
hasValidDash = true;
break;
}
}
if (hasValidDash)
{
break;
}
}
if (hasValidDash)
{
int dashIndex = 0;
auto rawPath = m_path.mutableRawPath();
for (const rcp<ContourMeasure>& contour : m_contours)
{
float dashed = 0.0f;
float distance = offset->normalizedLength(contour->length());
bool draw = true;
while (dashed < contour->length())
{
const Dash* dash = dashes[dashIndex++ % dashes.size()];
float dashLength = dash->normalizedLength(contour->length());
if (dashLength > contour->length())
{
dashLength = contour->length();
}
float endLength = distance + dashLength;
if (endLength > contour->length())
{
endLength -= contour->length();
if (draw)
{
if (distance < contour->length())
{
contour->getSegment(distance,
contour->length(),
rawPath,
true);
contour->getSegment(0.0f,
endLength,
rawPath,
!contour->isClosed());
}
else
{
contour->getSegment(0.0f, endLength, rawPath, true);
}
}
// Setup next step.
distance = endLength - dashLength;
}
else if (draw)
{
contour->getSegment(distance, endLength, rawPath, true);
}
distance += dashLength;
dashed += dashLength;
draw = !draw;
}
}
}
return &m_path;
}
float PathDasher::pathLength() const
{
float totalLength = 0.0f;
for (auto contour : m_contours)
{
totalLength += contour->length();
}
return totalLength;
}
StatusCode DashPath::onAddedClean(CoreContext* context)
{
if (!parent()->is<Stroke>())
{
return StatusCode::InvalidObject;
}
parent()->as<Stroke>()->addStrokeEffect(this);
m_dashes.clear();
for (auto child : children())
{
if (child->is<Dash>())
{
m_dashes.push_back(child->as<Dash>());
}
}
return StatusCode::Ok;
}
void DashPath::invalidateEffect() { invalidateSourcePath(); }
void DashPath::offsetChanged() { invalidateDash(); }
void DashPath::offsetIsPercentageChanged() { invalidateDash(); }
void DashPath::updateEffect(const ShapePaintPath* source)
{
if (m_path.hasRenderPath())
{
return;
}
m_path.rewind(source->isLocal());
Dash dashOffset(offset(), offsetIsPercentage());
applyDash(source->rawPath(), &dashOffset, m_dashes);
}
ShapePaintPath* DashPath::effectPath() { return &m_path; }
void DashPath::invalidateDash()
{
PathDasher::invalidateDash();
if (parent() != nullptr)
{
auto stroke = parent()->as<Stroke>();
stroke->parent()->addDirt(ComponentDirt::Paint);
stroke->invalidateRendering();
}
}