Skip to content

Layout Framework (08_04_00)

Sistema de layouts flexible y responsive para interfaces de usuario de plugins de audio.

📋 Características

  • FlexLayout: Sistema flexbox para layouts dinámicos (row/column)
  • GridLayout: Sistema de grid con filas/columnas configurables
  • ConstraintLayout: Layout basado en constraints (similar a Auto Layout)
  • ResponsiveEngine: Sistema responsive con breakpoints
  • AspectRatioConstraint: Mantención de aspect ratio
  • AdaptiveSpacing: Sistema de espaciado adaptativo

🚀 Uso Rápido

FlexLayout

#include "FlexLayout.hpp"

FlexLayout layout;
layout.setDirection(FlexLayout::Direction::Row);
layout.setJustify(FlexLayout::Justify::SpaceBetween);
layout.setAlign(FlexLayout::Align::Center);
layout.setSpacing(10.0f);

layout.addChild(&knob1, 1.0f);  // flexGrow = 1
layout.addChild(&knob2, 2.0f);  // flexGrow = 2 (ocupa 2x espacio)
layout.layout(Rect{0, 0, 800, 600});

GridLayout

#include "GridLayout.hpp"

GridLayout grid;
grid.setRows({100.0f, -1.0f, 100.0f});    // fixed, auto, fixed
grid.setColumns({-1.0f, -2.0f, -1.0f});   // 1fr, 2fr, 1fr
grid.setGap(10.0f, 10.0f);

grid.addChild(&widget1, 0, 0);           // row 0, col 0
grid.addChild(&widget2, 0, 1, 1, 2);     // row 0, col 1, span 2 cols
grid.layout(Rect{0, 0, 800, 600});

ConstraintLayout

#include "ConstraintLayout.hpp"

ConstraintLayout layout;
layout.addChild(&button);
layout.addChild(&label);

// Center button
layout.addConstraint(&button, ConstraintLayout::Attribute::CenterX,
                    nullptr, ConstraintLayout::Attribute::CenterX);
layout.addConstraint(&button, ConstraintLayout::Attribute::CenterY,
                    nullptr, ConstraintLayout::Attribute::CenterY);

// Label below button with 10px gap
layout.addConstraint(&label, ConstraintLayout::Attribute::Top,
                    &button, ConstraintLayout::Attribute::Bottom, 10.0f);
layout.layout(Rect{0, 0, 800, 600});

ResponsiveEngine

#include "ResponsiveEngine.hpp"

ResponsiveEngine engine;
engine.setBaseWidth(800.0f);
engine.setBaseHeight(600.0f);

engine.onBreakpointChange([](const std::string& name, SizeClass sizeClass) {
    std::cout << "Breakpoint: " << name << "\n";
});

engine.update(windowWidth, windowHeight);
float scaleFactor = engine.getScaleFactor();

🔧 API Principal

FlexLayout

enum class Direction { Row, Column, RowReverse, ColumnReverse };
enum class Justify { Start, End, Center, SpaceBetween, SpaceAround, SpaceEvenly };
enum class Align { Start, End, Center, Stretch };

void setDirection(Direction dir);
void setJustify(Justify j);
void setAlign(Align a);
void addChild(Widget* widget, float flexGrow = 0.0f, float flexShrink = 1.0f);
void layout(const Rect& bounds) override;

GridLayout

void setRows(const std::vector<float>& rows);      // Positive = fixed, 0 = auto, negative = fr
void setColumns(const std::vector<float>& columns);
void setGap(float rowGap, float colGap);
void addChild(Widget* widget, size_t row, size_t col, size_t rowSpan = 1, size_t colSpan = 1);
void layout(const Rect& bounds) override;

ConstraintLayout

enum class Attribute { Left, Right, Top, Bottom, CenterX, CenterY, Width, Height };
enum class Relation { Equal, LessThanOrEqual, GreaterThanOrEqual };

void addConstraint(Widget* first, Attribute firstAttr,
                  Widget* second, Attribute secondAttr,
                  float constant = 0.0f, float multiplier = 1.0f,
                  Relation relation = Relation::Equal);
void layout(const Rect& bounds) override;

ResponsiveEngine

enum class SizeClass { Compact, Medium, Expanded };
enum class Orientation { Portrait, Landscape };

void addBreakpoint(float width, const std::string& name, SizeClass sizeClass);
void update(float width, float height);
SizeClass getCurrentSizeClass() const;
Orientation getCurrentOrientation() const;
float getScaleFactor() const;
float scale(float value) const;

📦 Compilación

cd 08_04_00_layout_framework
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build .

🧪 Tests

cd build
ctest --output-on-failure

# O ejecutar directamente:
./tests/test_layout_framework.exe

📚 Ejemplos

./examples/layout_example.exe

🎯 Casos de Uso

Plugin UI con múltiples knobs

FlexLayout mainLayout;
mainLayout.setDirection(FlexLayout::Direction::Row);
mainLayout.setJustify(FlexLayout::Justify::SpaceAround);
mainLayout.setPadding(Insets{20.0f});

mainLayout.addChild(&freqKnob);
mainLayout.addChild(&resKnob);
mainLayout.addChild(&driveKnob);
mainLayout.addChild(&mixKnob);

mainLayout.layout(pluginBounds);

Dashboard con header/footer

GridLayout dashboard;
dashboard.setRows({60.0f, -1.0f, 40.0f});  // header, content, footer
dashboard.setColumns({-1.0f});             // full width

dashboard.addChild(&header, 0, 0);
dashboard.addChild(&content, 1, 0);
dashboard.addChild(&footer, 2, 0);

dashboard.layout(windowBounds);

Responsive UI

ResponsiveEngine engine;
FlexLayout layout;

engine.onBreakpointChange([&](const std::string& bp, SizeClass sc) {
    if (sc == SizeClass::Compact) {
        layout.setDirection(FlexLayout::Direction::Column);
    } else {
        layout.setDirection(FlexLayout::Direction::Row);
    }
});

engine.update(width, height);
layout.layout(bounds);

⚙️ Configuración

Padding y Spacing

// FlexLayout
layout.setSpacing(10.0f);           // Gap entre items
layout.setPadding(Insets{20.0f});   // Padding uniforme
layout.setPadding(Insets{10.0f, 20.0f, 10.0f, 20.0f});  // TRBL

// GridLayout
grid.setGap(10.0f, 20.0f);  // row gap, col gap
grid.setPadding(Insets{15.0f});

Breakpoints Personalizados

ResponsiveEngine engine;
engine.clearBreakpoints();  // Remover defaults
engine.addBreakpoint(480.0f, "phone", SizeClass::Compact);
engine.addBreakpoint(768.0f, "tablet", SizeClass::Medium);
engine.addBreakpoint(1024.0f, "laptop", SizeClass::Expanded);
engine.addBreakpoint(1920.0f, "desktop", SizeClass::Expanded);

🎨 Design Patterns

Nested Layouts

// Main grid
GridLayout mainGrid;
mainGrid.setRows({100.0f, -1.0f});
mainGrid.setColumns({-1.0f});

// Header con FlexLayout
FlexLayout headerLayout;
headerLayout.setDirection(FlexLayout::Direction::Row);
headerLayout.setJustify(FlexLayout::Justify::SpaceBetween);
// ... add header items

// Layout anidado
mainGrid.layout(bounds);
Rect headerBounds = mainGrid.getCellBounds(0, 0);
headerLayout.layout(headerBounds);

📊 Performance

  • FlexLayout: O(n) para n widgets
  • GridLayout: O(n) para n widgets
  • ConstraintLayout: O(n*m) para n widgets y m constraints (iterativo, max 10 iteraciones)
  • ResponsiveEngine: O(1) update, O(log n) para n breakpoints

🔍 Debugging

Todos los layouts exponen sus bounds calculados:

Rect bounds = widget->getBounds();
std::cout << "Widget at (" << bounds.x << ", " << bounds.y << ") "
          << "size (" << bounds.width << " x " << bounds.height << ")\n";

📝 Notas

  • Todos los layouts respetan Widget::participatesInLayout()
  • Los widgets invisibles no participan en layout
  • Los tamaños se respetan dentro de minSize y maxSize
  • GridLayout: valores negativos = fractional units (fr)
  • GridLayout: valor 0 = auto-sizing
  • ConstraintLayout usa resolución iterativa (no Cassowary completo)

🔗 Dependencias

  • C++20
  • Catch2 (solo para tests)

📄 Licencia

Parte del framework AudioLab.