Skip to content

UI Component System

The UI component system provides composable behaviors for UI elements using the Component Pattern.

Overview

Components add optional behaviors to any UIElement:

Component Purpose
LayoutComponent Automatic child positioning (vertical, horizontal, grid)
ConstraintComponent Position/size relative to parent edges
ScrollComponent Scrollable content with scrollbars

Note: While all UIElements can have components, some components are more useful on certain elements: - LayoutComponent and ScrollComponent are typically used with Container elements that have children - ConstraintComponent is useful on any element to anchor it within its parent (e.g., a close button in a modal)

Quick Start

using namespace engine::ui;
using namespace engine::ui::components;

auto menu = ContainerBuilder()
    .Size(300, 400)
    .Padding(10)
    .Layout<VerticalLayout>(8.0f, Alignment::Center)
    .Scrollable(ScrollDirection::Vertical)
    .Background(UITheme::Background::PANEL)
    .ClipChildren()
    .Build();

menu->AddChild(std::make_unique<Button>(100, 30, "Button 1"));
menu->AddChild(std::make_unique<Button>(100, 30, "Button 2"));
menu->Update();  // Apply layout

Note: UI elements have layout-friendly constructors that omit position (position is set by the layout): - Button(width, height, label) - size and label only - Label(text) - text only (auto-sizes) - Checkbox(label) - label only - Slider(width, height, min, max) - size and range only - Panel(), Container(), Image() - default constructors

Direct Component API

Components can be added to any UIElement, not just containers:

using namespace engine::ui::elements;
using namespace engine::ui::components;

// Add constraints to a button (anchor to top-right of parent)
auto button = std::make_unique<Button>(30, 20, "X");
button->AddComponent<ConstraintComponent>(Anchor::TopRight(5.0f));
panel->AddChild(std::move(button));

// Container example with layout and scroll
auto container = std::make_unique<Container>(0, 0, 300, 400);
container->AddComponent<LayoutComponent>(
    std::make_unique<VerticalLayout>(8.0f, Alignment::Center)
);
container->AddComponent<ScrollComponent>(ScrollDirection::Vertical);

// Query components
auto* layout = container->GetComponent<LayoutComponent>();
bool has_scroll = container->HasComponent<ScrollComponent>();

// Remove components
container->RemoveComponent<ScrollComponent>();

// Update components recursively (applies constraints to all children)
container->UpdateComponentsRecursive();

Component Invalidation

When element state changes, call InvalidateComponents() to notify components:

element->InvalidateComponents();  // Marks LayoutComponent dirty, etc.
element->Update();                // Components recalculate on next update

Layout Strategies

Layout strategies control how children are positioned within a container.

VerticalLayout

Stack children top to bottom:

container->AddComponent<LayoutComponent>(
    std::make_unique<VerticalLayout>(
        8.0f,              // Gap between children
        Alignment::Center  // Horizontal alignment
    )
);

HorizontalLayout

Arrange children left to right:

container->AddComponent<LayoutComponent>(
    std::make_unique<HorizontalLayout>(10.0f, Alignment::Center)
);

GridLayout

Arrange in rows and columns:

container->AddComponent<LayoutComponent>(
    std::make_unique<GridLayout>(
        3,      // Number of columns
        8.0f,   // Horizontal gap
        8.0f    // Vertical gap
    )
);

StackLayout

Overlay all children at the same position (useful for layered UI like background + content + overlay):

container->AddComponent<LayoutComponent>(
    std::make_unique<StackLayout>(
        Alignment::Center,  // Horizontal alignment
        Alignment::Center   // Vertical alignment
    )
);

// Example: Badge in top-right corner
container->AddComponent<LayoutComponent>(
    std::make_unique<StackLayout>(Alignment::End, Alignment::Start)
);

JustifyLayout

Distribute children evenly (like CSS flexbox justify-content: space-between):

container->AddComponent<LayoutComponent>(
    std::make_unique<JustifyLayout>(
        JustifyDirection::Horizontal,  // Or Vertical
        Alignment::Center              // Cross-axis alignment
    )
);

Note: Gap is calculated automatically to distribute children evenly.

Alignment Options

enum class Alignment {
    Start,   // Left/top
    Center,  // Center
    End,     // Right/bottom
    Stretch  // Fill available space
};

Padding Behavior

When a Panel/Container has padding, layouts handle it per-axis:

  • Primary axis: Padding insets start position and reduces available space
  • Cross axis:
  • Start/End/Stretch: Respect padding
  • Center: Uses full dimension (ignores padding for true centering)

Constraints

Constraints position and size elements relative to their parent.

Anchor Presets

Anchor::TopLeft(margin)           // Top-left corner
Anchor::TopRight(margin)          // Top-right corner
Anchor::BottomLeft(margin)        // Bottom-left corner
Anchor::BottomRight(margin)       // Bottom-right corner
Anchor::StretchHorizontal(l, r)   // Stretch between left/right
Anchor::StretchVertical(t, b)     // Stretch between top/bottom
Anchor::Fill(margin)              // Fill with uniform margin

Manual Anchor Configuration

Anchor anchor;
anchor.SetLeft(10.0f);    // 10px from left
anchor.SetRight(10.0f);   // 10px from right (causes stretch)
anchor.SetTop(20.0f);     // 20px from top

Size Constraints

SizeConstraint::Fixed(200.0f)        // Fixed 200px
SizeConstraint::Percent(0.5f)        // 50% of parent
SizeConstraint::FitContent(100, 300) // Fit content, min 100, max 300

SizeConstraints::Fixed(200.0f, 100.0f)   // Fixed width and height
SizeConstraints::Percent(0.8f, 0.5f)     // Percentage-based
SizeConstraints::Full()                  // 100% of parent

Using ConstraintComponent

container->AddComponent<ConstraintComponent>(
    Anchor::Fill(10.0f),
    SizeConstraints::Percent(0.8f, 0.5f)
);

Scrolling

Add scrolling behavior to containers with content larger than the viewport.

Basic Usage

auto* scroll = container->AddComponent<ScrollComponent>(ScrollDirection::Vertical);
scroll->SetContentSize(300, 1000);  // Total scrollable area

// Or auto-calculate from children
scroll->CalculateContentSizeFromChildren();

ScrollState API

ScrollState& state = scroll->GetState();

// Position control
state.SetScroll(0, 100);
state.ScrollBy(0, 50);
state.ScrollToStart();
state.ScrollToEnd();

// Queries
bool can_scroll = state.CanScrollY();
float max = state.GetMaxScrollY();
float normalized = state.GetScrollYNormalized();  // 0.0-1.0

Scrollbar Styling

ScrollbarStyle style;
style.track_color = {0.2f, 0.2f, 0.2f, 0.5f};
style.thumb_color = {0.5f, 0.5f, 0.5f, 0.8f};
style.width = 10.0f;
style.min_thumb_length = 30.0f;
scroll->SetStyle(style);

Scroll Directions

enum class ScrollDirection {
    Vertical,    // Up/down only
    Horizontal,  // Left/right only
    Both         // Both directions
};

ContainerBuilder Reference

The builder provides a fluent API for container construction:

ContainerBuilder()
    // Position and size
    .Position(x, y)
    .Size(width, height)
    .Bounds(x, y, width, height)

    // Panel styling
    .Padding(padding)
    .Background(color)
    .Border(width, color)
    .Opacity(opacity)
    .ClipChildren()

    // Layout
    .Layout<LayoutType>(args...)

    // Constraints
    .Anchor(anchor)
    .SizeConstraints(constraints)
    .Fill(margin)
    .StretchHorizontal(left, right)
    .StretchVertical(top, bottom)

    // Scrolling
    .Scrollable(direction)
    .ScrollStyle(style)

    // Build
    .Build();

Examples

Responsive Toolbar

auto toolbar = ContainerBuilder()
    .Bounds(0, 0, 0, 50)
    .Layout<HorizontalLayout>(8.0f, Alignment::Center)
    .StretchHorizontal(0, 0)
    .Padding(10)
    .Build();

Centered Modal Dialog

auto dialog = ContainerBuilder()
    .Size(400, 300)
    .Layout<VerticalLayout>(12.0f, Alignment::Center)
    .Fill(50.0f)
    .Background(UITheme::Background::PANEL)
    .Border(2.0f, UITheme::Border::ACCENT)
    .Build();

Scrollable List

auto list = ContainerBuilder()
    .Size(250, 400)
    .Layout<VerticalLayout>(4.0f, Alignment::Stretch)
    .Scrollable(ScrollDirection::Vertical)
    .ClipChildren()
    .Build();

for (const auto& item : items) {
    list->AddChild(CreateListItem(item));
}
list->Update();

Icon Grid

auto grid = ContainerBuilder()
    .Size(400, 400)
    .Layout<GridLayout>(4, 8.0f, 8.0f)
    .Scrollable(ScrollDirection::Vertical)
    .Build();

for (const auto& icon : icons) {
    grid->AddChild(std::make_unique<ImageElement>(0, 0, 80, 80, icon.texture));
}
grid->Update();

Component Lifecycle

Components participate in the element's update and render cycle:

Method When Called
OnAttach(owner) When added to element
OnDetach() When removed from element
OnUpdate(delta_time) Each frame before rendering
OnRender() After element renders (for overlays like scrollbars)
OnMouseEvent(event) When processing mouse input

Debug Tools

The engine includes debug utilities for UI development:

UIDebugVisualizer

Visualizes element bounds and hierarchy:

#include "ui_debug_visualizer.h"

UIDebugVisualizer debugger;
debugger.SetEnabled(true);

// Setup click-to-select (once after UI is built)
debugger.SetupClickToSelect(root.get());

// In render loop
BatchRenderer::BeginFrame();
root->Render();
debugger.RenderDebugOverlay(root.get());  // Render after UI
BatchRenderer::EndFrame();

// In ImGui
debugger.RenderImGuiControls();

UIElementInspector

Chrome DevTools-style inspector with inline editing:

#include "ui_element_inspector.h"

UIElementInspector inspector;

// In ImGui
ImGui::Begin("Inspector");
if (inspector.Render(selected_element)) {
    // Element was modified
    container->Update();
}
ImGui::End();

Features: - Box Model: Visual diagram with inline-editable padding, border, width, height, position - Anchor Widget: Click to toggle anchors, preset buttons for common layouts - Component List: Shows attached LayoutComponent, ConstraintComponent, ScrollComponent


See Also