| #include "rive/artboard.hpp" |
| #include "rive/layout/layout_component_style.hpp" |
| #include "rive/layout_component.hpp" |
| #include "rive_file_reader.hpp" |
| #include "rive_testing.hpp" |
| #include <catch.hpp> |
| |
| // Asset generated by rive_core/test/layout_stack_export_test.dart: |
| // a 400x400 artboard with a 200x200 stack (layoutType=stack, bottomRight |
| // alignment) containing a fill child and a fixed 40x40 box. Stack synthesizes a |
| // 1x1 grid, so every child shares the single cell. |
| TEST_CASE("stack overlaps children and aligns from a .riv file", |
| "[layoutstack]") |
| { |
| auto file = ReadRiveFile("assets/layout/stack.riv"); |
| auto artboard = file->artboard(); |
| REQUIRE(artboard != nullptr); |
| artboard->advance(0.0f); |
| |
| // The exporter strips names; identify layouts by structure. |
| auto layouts = artboard->find<rive::LayoutComponent>(); |
| rive::LayoutComponent* stack = nullptr; |
| rive::LayoutComponent* fill = nullptr; |
| rive::LayoutComponent* box = nullptr; |
| for (auto layout : layouts) |
| { |
| if (layout->is<rive::Artboard>() || layout->style() == nullptr) |
| { |
| continue; |
| } |
| if (layout->style()->isStack()) |
| { |
| stack = layout; |
| } |
| else if (layout->style()->widthScaleType() == |
| rive::LayoutScaleType::fill) |
| { |
| fill = layout; |
| } |
| else |
| { |
| box = layout; |
| } |
| } |
| REQUIRE(stack != nullptr); |
| REQUIRE(fill != nullptr); |
| REQUIRE(box != nullptr); |
| |
| // The fill child occupies the entire cell (the whole 200x200 stack), |
| // overlapping the box — proving both share the single stacked cell. |
| REQUIRE(fill->layoutX() == 0.0f); |
| REQUIRE(fill->layoutY() == 0.0f); |
| REQUIRE(fill->layoutWidth() == 200.0f); |
| REQUIRE(fill->layoutHeight() == 200.0f); |
| |
| // The fixed box keeps its size and sits at the bottom-right (alignment). |
| REQUIRE(box->layoutWidth() == 40.0f); |
| REQUIRE(box->layoutHeight() == 40.0f); |
| REQUIRE(box->layoutX() == 160.0f); |
| REQUIRE(box->layoutY() == 160.0f); |
| } |
| |
| // Sweep the 9-way alignment and confirm the fixed box lands in the right spot. |
| // Exercises start/center/end on both axes (grid uses End, not FlexEnd). |
| TEST_CASE("stack alignment positions a fixed child", "[layoutstack]") |
| { |
| auto file = ReadRiveFile("assets/layout/stack.riv"); |
| auto artboard = file->artboard(); |
| REQUIRE(artboard != nullptr); |
| artboard->advance(0.0f); |
| |
| auto layouts = artboard->find<rive::LayoutComponent>(); |
| rive::LayoutComponent* stack = nullptr; |
| rive::LayoutComponent* box = nullptr; |
| for (auto layout : layouts) |
| { |
| if (layout->is<rive::Artboard>() || layout->style() == nullptr) |
| { |
| continue; |
| } |
| if (layout->style()->isStack()) |
| { |
| stack = layout; |
| } |
| else if (layout->style()->widthScaleType() != |
| rive::LayoutScaleType::fill) |
| { |
| box = layout; |
| } |
| } |
| REQUIRE(stack != nullptr); |
| REQUIRE(box != nullptr); |
| |
| auto at = [&](rive::LayoutAlignmentType alignment, float x, float y) { |
| stack->style()->layoutAlignmentType((uint32_t)alignment); |
| artboard->advance(0.0f); |
| REQUIRE(box->layoutX() == x); |
| REQUIRE(box->layoutY() == y); |
| }; |
| |
| at(rive::LayoutAlignmentType::topLeft, 0.0f, 0.0f); |
| at(rive::LayoutAlignmentType::topCenter, 80.0f, 0.0f); |
| at(rive::LayoutAlignmentType::topRight, 160.0f, 0.0f); |
| at(rive::LayoutAlignmentType::centerLeft, 0.0f, 80.0f); |
| at(rive::LayoutAlignmentType::center, 80.0f, 80.0f); |
| at(rive::LayoutAlignmentType::centerRight, 160.0f, 80.0f); |
| at(rive::LayoutAlignmentType::bottomLeft, 0.0f, 160.0f); |
| at(rive::LayoutAlignmentType::bottomCenter, 80.0f, 160.0f); |
| at(rive::LayoutAlignmentType::bottomRight, 160.0f, 160.0f); |
| } |
| |
| // display() folds visibility (displayValue) + algorithm (layoutTypeValue) into |
| // the single YGDisplay the engine consumes: hidden wins, else flex vs grid. |
| // Uses an attached style (from a loaded file) since the property setters fire |
| // change handlers that walk to the owning layout. |
| TEST_CASE("engine display folds visibility and layout type", "[layoutstack]") |
| { |
| auto file = ReadRiveFile("assets/layout/stack.riv"); |
| auto artboard = file->artboard(); |
| REQUIRE(artboard != nullptr); |
| artboard->advance(0.0f); |
| |
| rive::LayoutComponentStyle* style = nullptr; |
| for (auto layout : artboard->find<rive::LayoutComponent>()) |
| { |
| if (!layout->is<rive::Artboard>() && layout->style() != nullptr && |
| layout->style()->isStack()) |
| { |
| style = layout->style(); |
| } |
| } |
| REQUIRE(style != nullptr); |
| |
| style->displayValue(0); // visible |
| style->layoutTypeValue(0 /* flex */); |
| REQUIRE(style->display() == YGDisplayFlex); |
| style->layoutTypeValue(1 /* grid */); |
| REQUIRE(style->display() == YGDisplayGrid); |
| style->layoutTypeValue(2 /* stack */); |
| REQUIRE(style->display() == YGDisplayGrid); |
| |
| style->displayValue(1); // hidden — wins regardless of type |
| style->layoutTypeValue(0); |
| REQUIRE(style->display() == YGDisplayNone); |
| style->layoutTypeValue(1); |
| REQUIRE(style->display() == YGDisplayNone); |
| style->layoutTypeValue(2); |
| REQUIRE(style->display() == YGDisplayNone); |
| } |