| #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.y, 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); | |
| } |