Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 9 additions & 13 deletions include/bombfork/prong/core/component.h
Original file line number Diff line number Diff line change
Expand Up @@ -497,25 +497,21 @@ class Component {
* @brief Set per-axis resize behavior for independent horizontal/vertical control
* @param horizontal Resize behavior for horizontal axis
* @param vertical Resize behavior for vertical axis
*
* NOTE: Original dimensions are NOT initialized here. They will be lazily initialized
* on the first call to onParentResize() AFTER the component has been laid out with
* its proper size. This ensures FIXED behavior captures the post-layout size, not
* the pre-layout size (which may be incomplete, e.g., height=0 for panels in FlexLayout).
*/
void setAxisResizeBehavior(AxisResizeBehavior horizontal, AxisResizeBehavior vertical) {
horizontalResizeBehavior = horizontal;
verticalResizeBehavior = vertical;
usePerAxisBehavior = true;

// Initialize original dimensions NOW so FIXED behavior works from the start
// This captures the component's current size as the "original" before any layout runs
if (originalWidth == 0 && originalHeight == 0) {
originalLocalX = localX;
originalLocalY = localY;
originalWidth = width;
originalHeight = height;

// Also initialize parent dimensions if we have a parent
if (parent) {
parent->getSize(originalParentWidth, originalParentHeight);
}
}
// NOTE: We deliberately do NOT initialize originalWidth/originalHeight here.
// They will be initialized on first onParentResize() call after layout has set proper sizes.
// This fixes the "jumping effect" where components created with partial sizes (e.g., width=300, height=0)
// would capture height=0 as "original", causing incorrect behavior on first resize.
}

/**
Expand Down
113 changes: 113 additions & 0 deletions tests/test_init_order_bug.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* @file test_init_order_bug.cpp
* @brief Test to reproduce the initialization order bug in issue #126
*/

#include <bombfork/prong/core/component.h>

#include <exception>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>

using namespace bombfork::prong;

// Simple concrete component for testing
class TestComponent : public Component {
public:
explicit TestComponent(const std::string& name = "TestComponent") : Component(nullptr, name) {}

void update(double deltaTime) override { (void)deltaTime; }
void render() override {}
};

void testInitializationOrderBug() {
std::cout << "Testing initialization order bug (issue #126)..." << std::endl;
std::cout << "Reproducing: setAxisResizeBehavior called BEFORE layout runs" << std::endl << std::endl;

// Simulate demo app scenario
auto parent = std::make_unique<TestComponent>("Parent");
parent->setBounds(0, 0, 800, 600);

// Create child with width=300, height=0 (simulating withSize(300, 0))
auto child = std::make_unique<TestComponent>("LeftPanel");
child->setBounds(0, 0, 300, 0); // Height is 0!

// Set axis resize behavior IMMEDIATELY (this used to be the bug!)
child->setAxisResizeBehavior(Component::AxisResizeBehavior::FIXED, Component::AxisResizeBehavior::FILL);

// Check current dimensions
std::cout << "After setAxisResizeBehavior (before layout):" << std::endl;
int w1, h1;
child->getSize(w1, h1);
std::cout << " Current size: " << w1 << " x " << h1 << std::endl;
std::cout << " NOTE: With fix, originalHeight is NOT initialized yet (lazy initialization)" << std::endl
<< std::endl;

// Now add to parent and simulate layout setting proper height
auto* childPtr = child.get();
parent->addChild(std::move(child));

// Simulate parent resize (which triggers onParentResize on children)
// This is what happens during initial layout when parent's size is set
std::cout << "Parent's size changes, triggering onParentResize(800, 600) on child..." << std::endl;
childPtr->onParentResize(800, 600);

int x2, y2, w2, h2;
childPtr->getBounds(x2, y2, w2, h2);
std::cout << " Child size after first onParentResize: " << w2 << " x " << h2 << std::endl;
std::cout << " Expected (FIXED horizontal=300, FILL vertical=600): 300 x 600" << std::endl;

if (w2 != 300 || h2 != 600) {
std::cout << " ✗ FAILED: First resize gave incorrect size!" << std::endl;
throw std::runtime_error("First resize failed");
}
std::cout << " ✓ First resize correct!" << std::endl << std::endl;

// Now simulate window resize (second resize)
std::cout << "Window resizes to 1024x768..." << std::endl;
childPtr->onParentResize(1024, 768);

int x3, y3, w3, h3;
childPtr->getBounds(x3, y3, w3, h3);
std::cout << " After second onParentResize: " << w3 << " x " << h3 << std::endl;
std::cout << " Expected (FIXED horizontal=300, FILL vertical=768): 300 x 768" << std::endl;

if (w3 != 300 || h3 != 768) {
std::cout << " ✗ FAILED: Second resize gave incorrect size!" << std::endl;
std::cout << " This is the 'jumping effect' - layout differs after window resize" << std::endl;
throw std::runtime_error("Second resize failed - jumping effect detected");
}
std::cout << " ✓ Second resize correct!" << std::endl << std::endl;

// Resize back smaller
std::cout << "Window resizes to 640x480..." << std::endl;
childPtr->onParentResize(640, 480);

int x4, y4, w4, h4;
childPtr->getBounds(x4, y4, w4, h4);
std::cout << " After third onParentResize: " << w4 << " x " << h4 << std::endl;
std::cout << " Expected (FIXED horizontal=300, FILL vertical=480): 300 x 480" << std::endl;

if (w4 != 300 || h4 != 480) {
std::cout << " ✗ FAILED: Third resize gave incorrect size!" << std::endl;
throw std::runtime_error("Third resize failed");
}
std::cout << " ✓ Third resize correct!" << std::endl << std::endl;

std::cout << "✓ Bug is FIXED: No jumping effect, all resizes consistent!" << std::endl;
}

int main() {
std::cout << "=== Issue #126: Initialization Order Bug Test ===" << std::endl << std::endl;

try {
testInitializationOrderBug();
return 0;
} catch (const std::exception& e) {
std::cerr << "✗ Test failed with exception: " << e.what() << std::endl;
return 1;
}
}