blob: 77432aef5fc1f5f92fb0066e7f9f6af3b6ea85ce [file] [log] [blame] [edit]
#include "rive/layout_component.hpp"
#include "rive/layout/n_sliced_node.hpp"
#include "rive/math/math_types.hpp"
#include "rive/math/n_slicer_helpers.hpp"
using namespace rive;
void NSlicedNode::markPathDirtyRecursive(bool sendToLayout)
{
// Tell all Shape descendants to re-render when the n slicer changes (e.g.
// when axis offset animates).
addDirt(ComponentDirt::NSlicer, true);
#ifdef WITH_RIVE_LAYOUT
if (sendToLayout)
{
for (ContainerComponent* p = parent(); p != nullptr; p = p->parent())
{
if (p->is<LayoutComponent>())
{
p->as<LayoutComponent>()->markLayoutNodeDirty();
break;
}
}
}
#endif
}
void NSlicedNode::axisChanged() { markPathDirtyRecursive(); }
void NSlicedNode::widthChanged() { markPathDirtyRecursive(); }
void NSlicedNode::heightChanged() { markPathDirtyRecursive(); }
AABB NSlicedNode::localBounds() const
{
return AABB(0, 0, initialWidth(), initialHeight());
}
void NSlicedNode::update(ComponentDirt value)
{
Super::update(value);
// Update whenever children axes change, or our world transform changes
// in case any dependent needs local path (we need to first map to the
// NSlicer space using world transform, then back).
if (hasDirt(value, ComponentDirt::NSlicer | ComponentDirt::WorldTransform))
{
updateMapWorldPoint();
}
}
void NSlicedNode::updateMapWorldPoint()
{
const Mat2D& world = worldTransform();
Mat2D inverseWorld;
if (!world.invert(&inverseWorld) || initialHeight() <= 0 ||
initialWidth() <= 0)
{
mapWorldPoint = [](Vec2D& v) {};
return;
}
Vec2D size = Vec2D(initialWidth(), initialHeight());
Vec2D scale = scaleForNSlicer();
std::vector<float> xPxStops = NSlicerHelpers::pxStops(xs(), size.x);
std::vector<float> yPxStops = NSlicerHelpers::pxStops(ys(), size.y);
std::vector<float> xUVStops = NSlicerHelpers::uvStops(xs(), size.x);
std::vector<float> yUVStops = NSlicerHelpers::uvStops(ys(), size.y);
ScaleInfo xScaleInfo =
NSlicerHelpers::analyzeUVStops(xUVStops, size.x, std::abs(scale.x));
ScaleInfo yScaleInfo =
NSlicerHelpers::analyzeUVStops(yUVStops, size.x, std::abs(scale.y));
mapWorldPoint = [this,
world,
inverseWorld,
scale,
xPxStops,
xScaleInfo,
yPxStops,
yScaleInfo](Vec2D& worldP) {
// 1. Map from world space to the effective NSlicer's space
Vec2D localP = inverseWorld * worldP;
// 2. N-Slice it in the NSlicer's space
Vec2D slicedP =
Vec2D(scale.x == 0 ? 0.0
: NSlicerHelpers::mapValue(xPxStops,
xScaleInfo,
std::abs(width()),
localP.x) *
std::copysign(1.0f, scale.x),
scale.y == 0 ? 0.0
: NSlicerHelpers::mapValue(yPxStops,
yScaleInfo,
std::abs(height()),
localP.y) *
std::copysign(1.0f, scale.y));
// 3. Back to world space
worldP = world * slicedP;
};
}
void NSlicedNode::deformWorldRenderPath(RawPath& path) const
{
NSlicerHelpers::deformWorldRenderPathWithNSlicer(*this, path);
}
void NSlicedNode::deformLocalRenderPath(RawPath& path,
const Mat2D& world,
const Mat2D& inverseWorld) const
{
NSlicerHelpers::deformLocalRenderPathWithNSlicer(*this,
path,
world,
inverseWorld);
}
Vec2D NSlicedNode::deformLocalPoint(Vec2D point,
const Mat2D& worldTransform,
const Mat2D& inverseWorld) const
{
Vec2D worldP = worldTransform * point;
Vec2D deformedWorldP = deformWorldPoint(worldP);
return inverseWorld * deformedWorldP;
}
Vec2D NSlicedNode::deformWorldPoint(Vec2D point) const
{
Vec2D result = point;
mapWorldPoint(result);
return result;
}
Vec2D NSlicedNode::scaleForNSlicer() const
{
return Vec2D(width() / initialWidth(), height() / initialHeight());
}
// We are telling a Layout what our size is
Vec2D NSlicedNode::measureLayout(float width,
LayoutMeasureMode widthMode,
float height,
LayoutMeasureMode heightMode)
{
return Vec2D(std::min((widthMode == LayoutMeasureMode::undefined
? std::numeric_limits<float>::max()
: width),
NSlicedNode::width()),
std::min((heightMode == LayoutMeasureMode::undefined
? std::numeric_limits<float>::max()
: height),
NSlicedNode::height()));
}
// We are told by a Layout to be a particular size
void NSlicedNode::controlSize(Vec2D size,
LayoutScaleType widthScaleType,
LayoutScaleType heightScaleType,
LayoutDirection direction)
{
width(size.x);
height(size.y);
markWorldTransformDirty();
markPathDirtyRecursive(false);
}