diff --git a/CLAUDE.md b/CLAUDE.md index 6253115..e5c0529 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -196,6 +196,62 @@ Prong uses a **hierarchical event propagation model** where events flow through Components must be enabled and visible to receive events. The Scene is the entry point for all events from the window system. +### Resize Behavior + +Components can specify how they respond to parent resize events through resize behaviors. This is essential for responsive UI layouts. + +#### Unified Resize Behavior + +The `ResizeBehavior` enum provides unified control over both axes: +- `FIXED`: Keep original size and position (default) +- `SCALE`: Scale proportionally with parent +- `FILL`: Fill available parent space +- `MAINTAIN_ASPECT`: Scale while maintaining aspect ratio + +```cpp +component->setResizeBehavior(Component::ResizeBehavior::FILL); +``` + +#### Per-Axis Resize Behavior + +For more control, use `AxisResizeBehavior` to set independent horizontal and vertical behavior: +- `AxisResizeBehavior::FIXED`: Keep original size on this axis +- `AxisResizeBehavior::SCALE`: Scale proportionally with parent on this axis +- `AxisResizeBehavior::FILL`: Fill available parent space on this axis + +```cpp +// Fixed width, fill height (common for panels in horizontal FlexLayout) +panel->setAxisResizeBehavior(Component::AxisResizeBehavior::FIXED, + Component::AxisResizeBehavior::FILL); +``` + +**Important**: When using FlexLayout, per-axis behavior is usually preferred: +- For horizontal FlexLayout (ROW): Use `FIXED` or `SCALE` horizontally (let FlexLayout control width), `FILL` vertically +- For vertical FlexLayout (COLUMN): Use `FILL` horizontally, `FIXED` or `SCALE` vertically (let FlexLayout control height) + +Example from demo app: +```cpp +// Left panel in horizontal FlexLayout: fixed width (200px), fills height +leftPanel->setAxisResizeBehavior(Component::AxisResizeBehavior::FIXED, + Component::AxisResizeBehavior::FILL); + +// Center panel: fills both dimensions (grows with FlexLayout) +centerPanel->setAxisResizeBehavior(Component::AxisResizeBehavior::FILL, + Component::AxisResizeBehavior::FILL); +``` + +#### Responsive Constraints + +Combine resize behavior with constraints for bounded resizing: +```cpp +Component::ResponsiveConstraints constraints; +constraints.minWidth = 200; +constraints.maxWidth = 600; +constraints.minHeight = 150; +constraints.maxHeight = 450; +component->setConstraints(constraints); +``` + ### Layout System Layout managers (`include/bombfork/prong/layout/`) use CRTP and provide: @@ -331,7 +387,7 @@ The namespace for the installed target is `bombfork::prong`. - **Focus model**: Components track their own focus state via `FocusState` enum; Scene manages global focus - **Thread safety**: ThemeManager is thread-safe; other components assume single-threaded use - Always use the `mise build` command to build the library, tests and examples. -- The demo app located in @examples/simple_app/ is used to test the library's UX, it is built and run with the `mise demo` command. It is meant to gather feedback from the user, and for overall feature validation. +- The demo app located in @examples/demo_app/ is used to test the library's UX, it is built and run with the `mise demo` command. It is meant to gather feedback from the user, and for overall feature validation. - Use gh cli to interact with the github repo when needed - ALWAYS use ninja generator when using cmake. - NEVER bypass iwyu or clang-format diff --git a/examples/demo_app/scenes/demo_scene.h b/examples/demo_app/scenes/demo_scene.h index 91070bb..c6ddcf0 100644 --- a/examples/demo_app/scenes/demo_scene.h +++ b/examples/demo_app/scenes/demo_scene.h @@ -76,7 +76,6 @@ class StatusLabel : public Component { // Calculate baseline Y position // Text baseline needs to be positioned so the text appears centered in the component - // For a 24px font in a 30px component, baseline at +18 from top works well int baselineY = gy; renderer->drawText(text_.c_str(), gx + 10, baselineY, textColor_.r, textColor_.g, textColor_.b, textColor_.a); @@ -125,8 +124,8 @@ class DemoScene : public Scene { lastDeltaTime = deltaTime; lastFpsUpdateTime += deltaTime; - // Update FPS counter every 0.1 seconds - if (lastFpsUpdateTime >= 0.1 && fpsLabelPtr) { + // Update FPS counter every 1 seconds + if (lastFpsUpdateTime >= 1 && fpsLabelPtr) { int fps = static_cast(std::round(1.0 / deltaTime)); fpsLabelPtr->setText("FPS: " + std::to_string(fps)); lastFpsUpdateTime = 0.0; @@ -223,10 +222,10 @@ class DemoScene : public Scene { // Create GLFW adapters for TextInput adapters = examples::glfw::GLFWAdapters::create(glfwWindow); - // === Main Layout - FlexLayout Horizontal === + // === Main Layout - FlexLayout Vertical === auto mainLayout = std::make_shared(); mainLayout->configure(FlexLayout::Configuration{ - .direction = FlexDirection::ROW, .justify = FlexJustify::START, .align = FlexAlign::STRETCH, .gap = 15.0f}); + .direction = FlexDirection::ROW, .justify = FlexJustify::START, .align = FlexAlign::STRETCH, .gap = 3.0f}); // Set flex item properties: left and right panels fixed width, center panel grows to fill mainLayout->setItemProperties({ @@ -244,26 +243,29 @@ class DemoScene : public Scene { toolbarPanelLayout->configure(FlexLayout::Configuration{ .direction = FlexDirection::ROW, .justify = FlexJustify::START, .align = FlexAlign::STRETCH, .gap = 0.0f}); - auto toolbarPanel = create().withSize(0, 40).withLayout(toolbarPanelLayout).build(); - toolbarPanel->setBackgroundColor(theming::Color(0.12f, 0.12f, 0.14f, 1.0f)); + auto toolbarPanel = create().withSize(0, 35).withLayout(toolbarPanelLayout).build(); toolbarPanel->setBorderColor(theming::Color(0.3f, 0.3f, 0.35f, 1.0f)); toolbarPanel->setBorderWidth(1); - toolbarPanel->setPadding(5); toolbarPanel->addChild(std::move(toolbar)); // === LEFT PANEL - Controls & Inputs === auto leftPanel = buildControlPanel(); + // Fixed width horizontally (controlled by FlexLayout grow/shrink=0), fill vertically + leftPanel->setAxisResizeBehavior(Component::AxisResizeBehavior::FIXED, Component::AxisResizeBehavior::FILL); // === CENTER PANEL - Layout Demonstrations === auto centerPanel = buildCenterPanel(); + // Fill horizontally (controlled by FlexLayout grow/shrink=1), fill vertically + centerPanel->setAxisResizeBehavior(Component::AxisResizeBehavior::FILL, Component::AxisResizeBehavior::FILL); // === RIGHT PANEL - Component Showcase === auto rightPanel = buildComponentShowcase(); + // Fixed width horizontally (controlled by FlexLayout grow/shrink=0), fill vertically + rightPanel->setAxisResizeBehavior(Component::AxisResizeBehavior::FIXED, Component::AxisResizeBehavior::FILL); // === Assemble 3-panel layout with FlexLayout === auto threePanelContainer = create().withLayout(mainLayout).build(); threePanelContainer->setBackgroundColor(theming::Color(0.08f, 0.08f, 0.1f, 1.0f)); - threePanelContainer->setPadding(15); threePanelContainer->addChild(std::move(leftPanel)); threePanelContainer->addChild(std::move(centerPanel)); @@ -280,7 +282,6 @@ class DemoScene : public Scene { statusBarPanel->setBackgroundColor(theming::Color(0.1f, 0.1f, 0.12f, 1.0f)); statusBarPanel->setBorderColor(theming::Color(0.3f, 0.3f, 0.35f, 1.0f)); statusBarPanel->setBorderWidth(1); - statusBarPanel->setPadding(5); // Left status label auto appNameLabel = std::make_unique("Prong UI Framework - Scene Demo"); @@ -315,9 +316,10 @@ class DemoScene : public Scene { // Add root container to scene addChild(std::move(rootContainer)); - // Initialize main container size to match scene/window + // Initialize main container size to match scene/window and set it to FILL behavior if (!children.empty() && children[0]) { children[0]->setBounds(0, 0, width, height); + children[0]->setResizeBehavior(Component::ResizeBehavior::FILL); children[0]->invalidateLayout(); } @@ -336,20 +338,19 @@ class DemoScene : public Scene { std::unique_ptr buildControlPanel() { auto leftLayout = std::make_shared(); leftLayout->configure(FlexLayout::Configuration{ - .direction = FlexDirection::COLUMN, .justify = FlexJustify::START, .align = FlexAlign::STRETCH, .gap = 10.0f}); - - auto leftPanel = create().withSize(280, 0).withLayout(leftLayout).build(); + .direction = FlexDirection::COLUMN, .justify = FlexJustify::START, .align = FlexAlign::STRETCH, .gap = 1.0f}); + auto leftPanel = create().withSize(300, 0).withLayout(leftLayout).build(); leftPanel->setBackgroundColor(theming::Color(0.15f, 0.15f, 0.18f, 1.0f)); leftPanel->setBorderColor(theming::Color(0.3f, 0.3f, 0.35f, 1.0f)); leftPanel->setBorderWidth(2); leftPanel->setTitle("Controls"); - leftPanel->setPadding(15); // === TextInput Demo === auto textInput = create() .withPlaceholder("Enter text here...") + .withSize(0, 30) .withTextChangedCallback([](const std::string& text) { std::cout << "Text: " << text << std::endl; }) .build(); textInputPtr = textInput.get(); @@ -363,7 +364,7 @@ class DemoScene : public Scene { buttonRowLayout->configure(FlexLayout::Configuration{.direction = FlexDirection::ROW, .justify = FlexJustify::SPACE_BETWEEN, .align = FlexAlign::STRETCH, - .gap = 10.0f}); + .gap = 1.0f}); auto buttonRow = create().withLayout(buttonRowLayout).build(); buttonRow->setBackgroundColor(theming::Color(0.0f, 0.0f, 0.0f, 0.0f)); @@ -452,7 +453,6 @@ class DemoScene : public Scene { centerPanel->setBorderColor(theming::Color(0.3f, 0.3f, 0.35f, 1.0f)); centerPanel->setBorderWidth(2); centerPanel->setTitle("Layout Demonstrations"); - centerPanel->setPadding(15); // === GridLayout Demo === auto gridPanel = buildGridLayoutDemo(); @@ -495,7 +495,6 @@ class DemoScene : public Scene { gridPanel->setBorderColor(theming::Color(0.3f, 0.3f, 0.35f, 1.0f)); gridPanel->setBorderWidth(1); gridPanel->setTitle("GridLayout (3x3 Button Grid)"); - gridPanel->setPadding(10); // Add 9 buttons in 3x3 grid for (int i = 1; i <= 9; ++i) { @@ -526,7 +525,6 @@ class DemoScene : public Scene { flowPanel->setBorderColor(theming::Color(0.3f, 0.3f, 0.35f, 1.0f)); flowPanel->setBorderWidth(1); flowPanel->setTitle("FlowLayout (Tag-like Interface)"); - flowPanel->setPadding(10); // Add various-sized "tag" buttons std::vector tags = {"C++20", "UI", "Framework", "Modern", @@ -558,7 +556,6 @@ class DemoScene : public Scene { stackPanel->setBorderColor(theming::Color(0.3f, 0.3f, 0.35f, 1.0f)); stackPanel->setBorderWidth(1); stackPanel->setTitle("StackLayout (Horizontal Stack)"); - stackPanel->setPadding(10); stackPanel->setLayoutManager(stackLayout); // Add horizontally stacked buttons @@ -586,7 +583,6 @@ class DemoScene : public Scene { wrapperPanel->setBorderColor(theming::Color(0.3f, 0.3f, 0.35f, 1.0f)); wrapperPanel->setBorderWidth(1); wrapperPanel->setTitle("DockLayout (Application-style Panel Docking)"); - wrapperPanel->setPadding(10); // Create DockLayout auto dockLayout = std::make_shared(); @@ -606,7 +602,6 @@ class DemoScene : public Scene { leftPanel->setBorderColor(theming::Color(0.4f, 0.5f, 0.6f, 1.0f)); leftPanel->setBorderWidth(1); leftPanel->setTitle("Files"); - leftPanel->setPadding(5); // Add file browser-style buttons auto fileLayout = std::make_shared(); @@ -630,7 +625,6 @@ class DemoScene : public Scene { topPanel->setBorderColor(theming::Color(0.5f, 0.4f, 0.6f, 1.0f)); topPanel->setBorderWidth(1); topPanel->setTitle("Tools"); - topPanel->setPadding(5); // Add horizontal tool buttons auto toolLayout = std::make_shared(); @@ -649,11 +643,11 @@ class DemoScene : public Scene { // === RIGHT DOCK - Properties Style === auto rightPanel = create>().withSize(0, 0).build(); + rightPanel->setBackgroundColor(theming::Color(0.3f, 0.25f, 0.2f, 1.0f)); rightPanel->setBorderColor(theming::Color(0.6f, 0.5f, 0.4f, 1.0f)); rightPanel->setBorderWidth(1); rightPanel->setTitle("Properties"); - rightPanel->setPadding(5); // Add property-style labels auto propLayout = std::make_shared(); @@ -675,7 +669,6 @@ class DemoScene : public Scene { centerPanel->setBorderColor(theming::Color(0.3f, 0.3f, 0.3f, 1.0f)); centerPanel->setBorderWidth(1); centerPanel->setTitle("Editor"); - centerPanel->setPadding(5); // Add center content label auto centerLabel = create