Expose localBounds in CPP (#10537) 0ed12de980

Implement some missing localBounds methods in CPP on core objects that expose this. Basically ported over from Dart. This was requested in order for the new rive_native Flutter runtime to provide a similar API to the legacy Flutter runtime.

Co-authored-by: Philip Chung <philterdesign@gmail.com>
This commit is contained in:
philter
2025-09-10 20:00:09 +00:00
parent 46b9efbee4
commit b76cecf1c4
16 changed files with 149 additions and 10 deletions

View File

@@ -1 +1 @@
e58f44d5ecca903e13d6770d7bf7d54d7fbaf76a
0ed12de9806f4e2caaf5e696b047abc9b79fa99d

View File

@@ -32,6 +32,7 @@ public:
const Mat2D& inverseWorld) const override;
Vec2D deformWorldPoint(Vec2D point) const override;
Vec2D scaleForNSlicer() const;
AABB localBounds() const override;
Vec2D measureLayout(float width,
LayoutMeasureMode widthMode,

View File

@@ -191,6 +191,7 @@ public:
m_layout.height());
}
size_t numLayoutNodes() override { return 1; }
AABB constraintBounds() const override { return localBounds(); }
AABB localBounds() const override
{
return AABB::fromLTWH(0.0f, 0.0f, m_layout.width(), m_layout.height());

View File

@@ -33,9 +33,7 @@ public:
#ifdef DEBUG
void printCode() const;
#endif
#ifdef WITH_RIVE_TOOLS
AABB preciseBounds() const;
#endif
size_t countMoveTos() const;
void move(Vec2D);

View File

@@ -42,6 +42,7 @@ public:
float width() const;
float height() const;
void assetUpdated() override;
AABB localBounds() const override;
#ifdef TESTING
Mesh* mesh() const;

View File

@@ -79,6 +79,7 @@ public:
#endif
void buildPath(RawPath&) const;
AABB localBounds() const override;
};
} // namespace rive

View File

@@ -59,6 +59,7 @@ public:
float length() override;
void setLength(float value) override {}
AABB localBounds() const override { return computeLocalBounds(); }
AABB worldBounds()
{
if ((static_cast<DrawableFlag>(drawableFlags()) &

View File

@@ -92,6 +92,7 @@ public:
const TextStylePaint* styleFromShaperId(uint16_t id) const;
bool modifierRangesNeedShape() const;
AABB localBounds() const override;
AABB constraintBounds() const override { return localBounds(); }
void originXChanged() override;
void originYChanged() override;

View File

@@ -55,6 +55,7 @@ public:
void scaleYChanged() override;
void addConstraint(Constraint* constraint);
virtual AABB constraintBounds() const { return AABB(); }
virtual AABB localBounds() const;
void markDirtyIfConstrained();
};

View File

@@ -8,7 +8,7 @@ using namespace rive;
const Mat2D TransformConstraint::targetTransform() const
{
AABB bounds = m_Target->localBounds();
AABB bounds = m_Target->constraintBounds();
Mat2D local =
Mat2D::fromTranslate(bounds.left() + bounds.width() * originX(),
bounds.top() + bounds.height() * originY());

View File

@@ -30,6 +30,11 @@ 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);

View File

@@ -539,7 +539,7 @@ void RawPath::printCode() const
fprintf(stderr, "\n");
}
#endif
#ifdef WITH_RIVE_TOOLS
static void expandAxisBounds(AABB& bounds, int axis, float value)
{
switch (axis)
@@ -713,7 +713,6 @@ AABB RawPath::preciseBounds() const
}
return bounds;
}
#endif
float RawPath::computeCoarseArea() const
{

View File

@@ -150,7 +150,7 @@ float Image::width() const
rive::RenderImage* renderImage = asset->renderImage();
if (renderImage == nullptr)
{
return 0.0f;
return asset->width();
}
return (float)renderImage->width();
}
@@ -166,7 +166,7 @@ float Image::height() const
rive::RenderImage* renderImage = asset->renderImage();
if (renderImage == nullptr)
{
return 0.0f;
return asset->height();
}
return (float)renderImage->height();
}
@@ -243,6 +243,18 @@ void Image::updateImageScale()
}
}
AABB Image::localBounds() const
{
if (imageAsset() == nullptr)
{
return AABB();
}
return AABB::fromLTWH(-width() * originX(),
-height() * originY(),
width(),
height());
}
ImageAsset* Image::imageAsset() const { return (ImageAsset*)m_fileAsset.get(); }
#ifdef TESTING

View File

@@ -127,6 +127,8 @@ bool Path::canDeferPathUpdate()
const Mat2D& Path::pathTransform() const { return worldTransform(); }
AABB Path::localBounds() const { return m_rawPath.preciseBounds(); }
void Path::buildPath(RawPath& rawPath) const
{
const bool isClosed = isPathClosed();

Binary file not shown.

View File

@@ -1,7 +1,12 @@
#include "rive/file.hpp"
#include "rive/node.hpp"
#include "rive/shapes/shape.hpp"
#include "rive/layout/n_sliced_node.hpp"
#include "rive/layout_component.hpp"
#include "rive/math/transform_components.hpp"
#include "rive/node.hpp"
#include "rive/shapes/image.hpp"
#include "rive/shapes/path.hpp"
#include "rive/shapes/shape.hpp"
#include "rive/text/text.hpp"
#include "rive/text/text_value_run.hpp"
#include "utils/no_op_renderer.hpp"
#include "rive_file_reader.hpp"
@@ -67,4 +72,115 @@ TEST_CASE("compute precise bounds of a raw path", "[bounds]")
CHECK(preciseBounds.top() == Approx(-1.78456f));
CHECK(preciseBounds.right() == Approx(428.90216f));
CHECK(preciseBounds.bottom() == Approx(466.05313f));
}
TEST_CASE("test local bounds", "[bounds]")
{
auto file = ReadRiveFile("assets/local_bounds.riv");
auto artboard = file->artboard();
REQUIRE(artboard->find<rive::Shape>("Shape1") != nullptr);
REQUIRE(artboard->find<rive::Shape>("Shape2") != nullptr);
REQUIRE(artboard->find<rive::Shape>("Shape3") != nullptr);
REQUIRE(artboard->find<rive::Text>("Text1") != nullptr);
REQUIRE(artboard->find<rive::Text>("Text2") != nullptr);
REQUIRE(artboard->find<rive::Node>("Group1") != nullptr);
REQUIRE(artboard->find<rive::Image>("Image1") != nullptr);
REQUIRE(artboard->find<rive::Image>("Image1")->imageAsset() != nullptr);
REQUIRE(artboard->find<rive::NSlicedNode>("NSlice2") != nullptr);
REQUIRE(artboard->find<rive::Shape>("CustomShape1") != nullptr);
REQUIRE(artboard->find<rive::Path>("CustomPath1") != nullptr);
REQUIRE(artboard->find<rive::LayoutComponent>("LayoutContainer") !=
nullptr);
REQUIRE(artboard->find<rive::LayoutComponent>("LayoutCellLeft") != nullptr);
auto shape1 = artboard->find<rive::Shape>("Shape1");
auto shape2 = artboard->find<rive::Shape>("Shape2");
auto shape3 = artboard->find<rive::Shape>("Shape3");
auto text1 = artboard->find<rive::Text>("Text1");
auto text2 = artboard->find<rive::Text>("Text2");
auto group1 = artboard->find<rive::Node>("Group1");
auto image1 = artboard->find<rive::Image>("Image1");
auto nslice2 = artboard->find<rive::NSlicedNode>("NSlice2");
auto customShape1 = artboard->find<rive::Shape>("CustomShape1");
auto customPath1 = artboard->find<rive::Path>("CustomPath1");
auto layoutContainer =
artboard->find<rive::LayoutComponent>("LayoutContainer");
auto layoutCell = artboard->find<rive::LayoutComponent>("LayoutCellLeft");
artboard->advance(0.0f);
// Origin 0.5,0.5
auto shape1Bounds = shape1->localBounds();
CHECK(shape1Bounds.left() == -35.0f);
CHECK(shape1Bounds.top() == -35.0f);
CHECK(shape1Bounds.right() == 35.0f);
CHECK(shape1Bounds.bottom() == 35.0f);
// Origin 1.0,1.0
auto shape2Bounds = shape2->localBounds();
CHECK(shape2Bounds.left() == -80.0f);
CHECK(shape2Bounds.top() == -80.0f);
CHECK(shape2Bounds.right() == 0.0f);
CHECK(shape2Bounds.bottom() == 0.0f);
// Origin 0.0,0.0
auto shape3Bounds = shape3->localBounds();
CHECK(shape3Bounds.left() == 0.0f);
CHECK(shape3Bounds.top() == 0.0f);
CHECK(shape3Bounds.right() == 60.0f);
CHECK(shape3Bounds.bottom() == 60.0f);
// Origin 0.0,0.0
auto text1Bounds = text1->localBounds();
CHECK(text1Bounds.left() == 0.0f);
CHECK(text1Bounds.top() == 0.0f);
CHECK(text1Bounds.right() == Approx(159.55078f));
CHECK(text1Bounds.bottom() == Approx(24.19921f));
// Origin 0.5,0.5
auto text2Bounds = text2->localBounds();
CHECK(text2Bounds.left() == Approx(-79.77539f));
CHECK(text2Bounds.top() == Approx(-12.099609f));
CHECK(text2Bounds.right() == Approx(79.77539f));
CHECK(text2Bounds.bottom() == Approx(12.099609f));
auto group1Bounds = group1->localBounds();
CHECK(group1Bounds.left() == 0.0f);
CHECK(group1Bounds.top() == 0.0f);
CHECK(group1Bounds.right() == 0.0f);
CHECK(group1Bounds.bottom() == 0.0f);
// Origin 0.5,0.5
auto image1Bounds = image1->localBounds();
CHECK(image1Bounds.left() == -64.0f);
CHECK(image1Bounds.top() == -64.0f);
CHECK(image1Bounds.right() == 64.0f);
CHECK(image1Bounds.bottom() == 64.0f);
auto nslice2Bounds = nslice2->localBounds();
CHECK(nslice2Bounds.left() == 0.0f);
CHECK(nslice2Bounds.top() == 0.0f);
CHECK(nslice2Bounds.right() == Approx(112.1891f));
CHECK(nslice2Bounds.bottom() == Approx(77.7086f));
auto customShape1Bounds = customShape1->localBounds();
CHECK(customShape1Bounds.left() == Approx(-27.82596f));
CHECK(customShape1Bounds.top() == Approx(-32.0276f));
CHECK(customShape1Bounds.right() == Approx(105.36988f));
CHECK(customShape1Bounds.bottom() == Approx(52.38258f));
auto customPath1Bounds = customPath1->localBounds();
CHECK(customPath1Bounds.left() == Approx(-11.52589f));
CHECK(customPath1Bounds.top() == Approx(-25.32601f));
CHECK(customPath1Bounds.right() == Approx(100.66321f));
CHECK(customPath1Bounds.bottom() == Approx(52.38258f));
auto layoutContainerBounds = layoutContainer->localBounds();
CHECK(layoutContainerBounds.left() == 0.0f);
CHECK(layoutContainerBounds.top() == 0.0f);
CHECK(layoutContainerBounds.right() == 200.0f);
CHECK(layoutContainerBounds.bottom() == 100.0f);
auto layoutCellBounds = layoutCell->localBounds();
CHECK(layoutCellBounds.left() == 0.0f);
CHECK(layoutCellBounds.top() == 0.0f);
CHECK(layoutCellBounds.right() == 88.0f);
CHECK(layoutCellBounds.bottom() == 84.0f);
}