Skip to content

User Interface

PixelRoot32 provides a complete UI system for creating menus, HUDs, and interface elements. This guide covers all UI components and layout systems.

UI Elements

All UI elements inherit from UIElement, which itself inherits from Entity. This means UI elements can be added to scenes just like any other entity.

UILabel

Display text on screen. The text is passed as std::string_view, allowing efficient usage with string literals or std::string.

#include <graphics/ui/UILabel.h>
#include <memory>

// Ideally, store this as a member variable in your Scene class:
// std::unique_ptr<pixelroot32::graphics::ui::UILabel> scoreLabel;

// Create a label using std::make_unique
scoreLabel = std::make_unique<pixelroot32::graphics::ui::UILabel>(
    "Score: 0",                    // text
    10,                            // x position
    10,                            // y position
    pixelroot32::graphics::Color::White,  // color
    1                              // size multiplier
);

// Add to scene (pass raw pointer)
addEntity(scoreLabel.get());

// Update text dynamically (if stored as member)
// scoreLabel->setText("Score: 100");

// Center horizontally
scoreLabel->centerX(240); // Screen width

UIButton

Create clickable buttons:

#include <graphics/ui/UIButton.h>

// In your Scene class:
// std::unique_ptr<pixelroot32::graphics::ui::UIButton> startButton;

// Create a button
startButton = std::make_unique<pixelroot32::graphics::ui::UIButton>(
    "Start Game",                  // text
    4,                             // navigation index
    50,                            // x position
    100,                           // y position
    140,                           // width
    30,                            // height
    []() {                         // callback function
        // Button clicked - start game
        startGame();
    }
);

// Configure style
startButton->setStyle(
    pixelroot32::graphics::Color::White,   // text color
    pixelroot32::graphics::Color::Blue,    // background color
    true                                    // draw background
);

// Add to scene
addEntity(startButton.get());

UICheckBox

Create interactive checkboxes:

#include <graphics/ui/UICheckBox.h>

// In your Scene class:
// std::unique_ptr<pixelroot32::graphics::ui::UICheckBox> soundCheckbox;

// Create a checkbox
soundCheckbox = std::make_unique<pixelroot32::graphics::ui::UICheckBox>(
    "Enable Sound",                // text
    4,                             // navigation index
    50,                            // x position
    140,                           // y position
    140,                           // width
    20,                            // height
    true,                          // initial checked state
    [](bool checked) {             // callback function
        // Checkbox state changed
        setSoundEnabled(checked);
    },
    1                              // font size
);

// Configure style
soundCheckbox->setStyle(
    pixelroot32::graphics::Color::White,   // text color
    pixelroot32::graphics::Color::Blue,    // background color
    false                                  // draw background
);

// Add to scene
addEntity(soundCheckbox.get());

UIPanel

Create visual containers with background and border:

#include <graphics/ui/UIPanel.h>

// In your Scene class:
// std::unique_ptr<pixelroot32::graphics::ui::UIPanel> dialog;

// Create a panel
dialog = std::make_unique<pixelroot32::graphics::ui::UIPanel>(
    50,   // x
    50,   // y
    140,  // width
    140   // height
);

// Configure appearance
dialog->setBackgroundColor(pixelroot32::graphics::Color::Black);
dialog->setBorderColor(pixelroot32::graphics::Color::White);
dialog->setBorderWidth(2);

// Add content (typically a layout)
dialog->setChild(menuLayout); // Ensure menuLayout is also managed!

// Add to scene
addEntity(dialog.get());

Layouts

Layouts automatically organize UI elements, eliminating the need for manual position calculations.

UIVerticalLayout

Organize elements vertically with automatic scrolling:

#include <graphics/ui/UIVerticalLayout.h>
#include <vector>

// In your Scene class:
// std::unique_ptr<pixelroot32::graphics::ui::UIVerticalLayout> menu;
// std::vector<std::unique_ptr<UIButton>> menuButtons;

// Create vertical layout
menu = std::make_unique<pixelroot32::graphics::ui::UIVerticalLayout>(
    10,   // x
    60,   // y
    220,  // width
    160   // height (viewport)
);

// Configure layout
menu->setPadding(5);        // Internal padding
menu->setSpacing(6);        // Space between elements
menu->setScrollEnabled(true); // Enable scrolling

// Set navigation buttons
menu->setNavigationButtons(0, 1); // UP=0, DOWN=1

// Set button styles
menu->setButtonStyle(
    pixelroot32::graphics::Color::White,  // selected text
    pixelroot32::graphics::Color::Cyan,    // selected background
    pixelroot32::graphics::Color::White,  // unselected text
    pixelroot32::graphics::Color::Black   // unselected background
);

// Add buttons (no manual positioning needed!)
for (int i = 0; i < 10; i++) {
    auto btn = std::make_unique<UIButton>(
        "Option " + std::to_string(i),
        i,
        0, 0,  // Position ignored - layout handles it
        200, 20,
        [i]() { handleOption(i); }
    );
    menu->addElement(btn.get());
    menuButtons.push_back(std::move(btn)); // Store ownership
}

// Add layout to scene
menu->setRenderLayer(2); // UI layer
addEntity(menu.get());

UIHorizontalLayout

Organize elements horizontally:

#include <graphics/ui/UIHorizontalLayout.h>
#include <memory>

// In your Scene class:
// std::unique_ptr<pixelroot32::graphics::ui::UIHorizontalLayout> menuBar;
// std::vector<std::unique_ptr<UIButton>> menuButtons;

// Create horizontal layout (menu bar)
menuBar = std::make_unique<pixelroot32::graphics::ui::UIHorizontalLayout>(
    0,    // x
    0,    // y
    240,  // width
    30    // height
);

menuBar->setPadding(5);
menuBar->setSpacing(4);
menuBar->setScrollEnabled(true);
menuBar->setNavigationButtons(2, 3); // LEFT=2, RIGHT=3

// Add menu items
auto fileBtn = std::make_unique<UIButton>("File", 0, 0, 0, 60, 20, []() {});
auto editBtn = std::make_unique<UIButton>("Edit", 1, 0, 0, 60, 20, []() {});
auto viewBtn = std::make_unique<UIButton>("View", 2, 0, 0, 60, 20, []() {});

menuBar->addElement(fileBtn.get());
menuBar->addElement(editBtn.get());
menuBar->addElement(viewBtn.get());

// Keep ownership
menuButtons.push_back(std::move(fileBtn));
menuButtons.push_back(std::move(editBtn));
menuButtons.push_back(std::move(viewBtn));

addEntity(menuBar.get());

UIGridLayout

Organize elements in a grid (matrix):

#include <graphics/ui/UIGridLayout.h>
#include <memory>
#include <vector>

// In your Scene class:
// std::unique_ptr<pixelroot32::graphics::ui::UIGridLayout> inventory;
// std::vector<std::unique_ptr<UIButton>> inventoryItems;

// Create grid layout (inventory)
inventory = std::make_unique<pixelroot32::graphics::ui::UIGridLayout>(
    10,   // x
    60,   // y
    220,  // width
    160   // height
);

inventory->setColumns(4);  // 4 columns
inventory->setPadding(5);
inventory->setSpacing(4);
inventory->setNavigationButtons(0, 1, 2, 3); // UP, DOWN, LEFT, RIGHT

// Add items (automatically arranged in grid)
for (int i = 0; i < 16; i++) {
    auto item = std::make_unique<UIButton>(
        "Item " + std::to_string(i),
        i,
        0, 0,  // Position ignored
        50, 50,
        [i]() { useItem(i); }
    );
    inventory->addElement(item.get());
    inventoryItems.push_back(std::move(item)); // Keep ownership
}

addEntity(inventory.get());

UIAnchorLayout

Position elements at fixed screen positions (perfect for HUDs):

#include <graphics/ui/UIAnchorLayout.h>

// Create anchor layout for HUD
pixelroot32::graphics::ui::UIAnchorLayout* hud = new pixelroot32::graphics::ui::UIAnchorLayout(
    0,    // x
    0,    // y
    240,  // screen width
    240   // screen height
);

hud->setScreenSize(240, 240);

// Add HUD elements at different anchor points
UILabel* scoreLabel = new UILabel("Score: 0", 0, 0, Color::White, 1);
UILabel* livesLabel = new UILabel("Lives: 3", 0, 0, Color::White, 1);

hud->addElement(scoreLabel, pixelroot32::graphics::ui::Anchor::TOP_LEFT);
hud->addElement(livesLabel, pixelroot32::graphics::ui::Anchor::TOP_RIGHT);

addEntity(hud);

Available Anchors: - TOP_LEFT, TOP_RIGHT, TOP_CENTER - BOTTOM_LEFT, BOTTOM_RIGHT, BOTTOM_CENTER - LEFT_CENTER, RIGHT_CENTER - CENTER

UIPaddingContainer

Add padding around a single element:

#include <graphics/ui/UIPaddingContainer.h>
#include <memory>

// In your Scene class:
// std::unique_ptr<pixelroot32::graphics::ui::UIPaddingContainer> container;
// std::unique_ptr<UIButton> button;

// Create padding container
container = std::make_unique<pixelroot32::graphics::ui::UIPaddingContainer>(
    10,   // x
    10,   // y
    200,  // width
    100   // height
);

// Set uniform padding
container->setPadding(10);

// Or set asymmetric padding
container->setPadding(5, 15, 10, 10); // left, right, top, bottom

// Add child element
// button = std::make_unique<UIButton>(...);
container->setChild(button.get());

addEntity(container.get());

Fixed Position UI (HUDs)

By default, UI elements behave like world objects: if you move the Camera2D, the UI elements will scroll with the world. For elements that should stay fixed on screen (like a HUD, health bar, or pause menu), you can use the fixedPosition property.

When setFixedPosition(true) is called on an element: 1. It ignores any Camera2D offsets (xOffset/yOffset). 2. It remains at its logical screen coordinates regardless of camera movement. 3. If the element is a layout (like UIAnchorLayout), all its children will also become fixed on screen.

Example: Fixed HUD

// Create a HUD layout
auto hud = std::make_unique<pixelroot32::graphics::ui::UIVerticalLayout>(10, 10, 100, 50);

// IMPORTANT: Make the HUD stay fixed on screen
hud->setFixedPosition(true);

// Add health label at TOP_LEFT
auto healthLabel = std::make_unique<UILabel>("HP: 100", 0, 0, Color::Red, 1);
hud->addElement(healthLabel.get());

// Add score label at TOP_RIGHT
auto scoreLabel = std::make_unique<UILabel>("Score: 0", 0, 0, Color::White, 1);
hud->addElement(scoreLabel.get());

addEntity(hud.get());
// Store pointers!

Layouts handle D-pad navigation automatically:

Vertical Navigation

verticalLayout->setNavigationButtons(Buttons::UP, Buttons::DOWN);

// Layout automatically:
// - Highlights selected button
// - Scrolls to keep selected button visible
// - Handles wrapping (optional)

Horizontal Navigation

horizontalLayout->setNavigationButtons(Buttons::LEFT, Buttons::RIGHT);

Grid Navigation

gridLayout->setNavigationButtons(Buttons::UP, Buttons::DOWN, Buttons::LEFT, Buttons::RIGHT);

// Layout automatically:
// - Handles 4-direction navigation
// - Wraps around edges
// - Updates selection

Manual Selection

// Set selected element programmatically
layout->setSelectedIndex(2);

// Get selected element
int selected = layout->getSelectedIndex();
UIElement* element = layout->getSelectedElement();

Complete Example: Main Menu

#include <core/Scene.h>
#include <graphics/ui/UIVerticalLayout.h>
#include <graphics/ui/UIButton.h>
#include <graphics/ui/UILabel.h>
#include <graphics/ui/UIPanel.h>

class MainMenuScene : public pixelroot32::core::Scene {
private:
    pixelroot32::graphics::ui::UIVerticalLayout* menuLayout;
    pixelroot32::graphics::ui::UIPanel* menuPanel;

public:
    void init() override {
        // Create panel
        menuPanel = new pixelroot32::graphics::ui::UIPanel(40, 40, 160, 160);
        menuPanel->setBackgroundColor(pixelroot32::graphics::Color::Black);
        menuPanel->setBorderColor(pixelroot32::graphics::Color::White);
        menuPanel->setBorderWidth(2);
        menuPanel->setRenderLayer(2);
        addEntity(menuPanel);

        // Create layout inside panel
        menuLayout = new pixelroot32::graphics::ui::UIVerticalLayout(0, 0, 160, 160);
        menuLayout->setPadding(10);
        menuLayout->setSpacing(8);
        menuLayout->setNavigationButtons(0, 1);
        menuLayout->setButtonStyle(
            pixelroot32::graphics::Color::White,
            pixelroot32::graphics::Color::Cyan,
            pixelroot32::graphics::Color::White,
            pixelroot32::graphics::Color::Black
        );

        // Add title
        pixelroot32::graphics::ui::UILabel* title = new pixelroot32::graphics::ui::UILabel(
            "GAME MENU", 0, 0, pixelroot32::graphics::Color::Yellow, 2
        );
        menuLayout->addElement(title);

        // Add menu buttons
        menuLayout->addElement(new pixelroot32::graphics::ui::UIButton(
            "Start Game", 0, 0, 0, 140, 25, []() { startGame(); }
        ));

        menuLayout->addElement(new pixelroot32::graphics::ui::UIButton(
            "Options", 1, 0, 0, 140, 25, []() { showOptions(); }
        ));

        menuLayout->addElement(new pixelroot32::graphics::ui::UIButton(
            "Quit", 2, 0, 0, 140, 25, []() { quitGame(); }
        ));

        menuPanel->setChild(menuLayout);
    }

    void update(unsigned long deltaTime) override {
        // Handle input for layout navigation
        auto& input = engine.getInputManager();
        menuLayout->handleInput(input);

        Scene::update(deltaTime);
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        Scene::draw(renderer);
    }
};

Complete Example: HUD

class GameHUD : public pixelroot32::core::Entity {
private:
    pixelroot32::graphics::ui::UIAnchorLayout* hud;
    pixelroot32::graphics::ui::UILabel* scoreLabel;
    pixelroot32::graphics::ui::UILabel* livesLabel;
    pixelroot32::graphics::ui::UILabel* healthLabel;

public:
    GameHUD()
        : Entity(0, 0, 240, 240, pixelroot32::core::EntityType::UI_ELEMENT) {
        setRenderLayer(2); // UI layer

        // Create HUD layout
        hud = new pixelroot32::graphics::ui::UIAnchorLayout(0, 0, 240, 240);
        hud->setScreenSize(240, 240);

        // Create labels
        scoreLabel = new pixelroot32::graphics::ui::UILabel(
            "Score: 0", 0, 0, pixelroot32::graphics::Color::White, 1
        );

        livesLabel = new pixelroot32::graphics::ui::UILabel(
            "Lives: 3", 0, 0, pixelroot32::graphics::Color::White, 1
        );

        healthLabel = new pixelroot32::graphics::ui::UILabel(
            "Health: 100%", 0, 0, pixelroot32::graphics::Color::Green, 1
        );

        // Position labels
        hud->addElement(scoreLabel, pixelroot32::graphics::ui::Anchor::TOP_LEFT);
        hud->addElement(livesLabel, pixelroot32::graphics::ui::Anchor::TOP_RIGHT);
        hud->addElement(healthLabel, pixelroot32::graphics::ui::Anchor::BOTTOM_CENTER);
    }

    void update(unsigned long deltaTime) override {
        // Update HUD text (example)
        char buffer[32];
        snprintf(buffer, sizeof(buffer), "Score: %d", currentScore);
        scoreLabel->setText(buffer);
    }

    void draw(pixelroot32::graphics::Renderer& renderer) override {
        // HUD draws itself through its layout
    }

    // Add HUD to scene
    void addToScene(Scene* scene) {
        scene->addEntity(hud);
    }
};

Best Practices

Organization

  • Use render layer 2: Keep all UI on the top layer
  • Group related elements: Use panels to group menu items
  • Separate HUD from menus: Use different layouts for different UI types
  • Reuse layouts: Create layout factories for common patterns

Performance

  • Layouts use viewport culling: Only visible elements are rendered
  • Minimize text updates: Updating text has overhead
  • Use appropriate layouts: Choose the right layout for your needs
  • Limit element count: Too many elements can impact performance

Fixed Position UI (HUDs & Overlays)

By default, UI elements placed in a scene will move with the Camera2D just like any other entity. To create a HUD or a menu that stays fixed on the screen regardless of camera movement, use the fixedPosition flag on your layouts:

// Create a HUD layout
auto hud = std::make_unique<pixelroot32::graphics::ui::UIVerticalLayout>(10, 10, 100, 50);

// Enable fixed positioning
hud->setFixedPosition(true); // <--- This layout will now ignore Camera2D scrolling

// Add to scene
addEntity(hud.get());
// Remember to store hud unique_ptr in the class!

When setFixedPosition(true) is called: 1. The layout and all its children will ignore the global camera offset. 2. The coordinates (x, y) of the layout become relative to the screen, not the world. 3. This is the recommended way to implement HUDs, pause menus, and screen-space overlays.

  • Set navigation buttons: Configure D-pad navigation for layouts
  • Handle input in update(): Check for button presses to trigger actions
  • Provide visual feedback: Selected buttons should be clearly visible
  • Test navigation flow: Ensure navigation feels responsive

Common Patterns

class MenuSystem {
    UIVerticalLayout* currentMenu;

public:
    void showMainMenu() {
        currentMenu = createMainMenu();
        scene->addEntity(currentMenu);
    }

    void showPauseMenu() {
        currentMenu = createPauseMenu();
        scene->addEntity(currentMenu);
    }

    void hideMenu() {
        if (currentMenu) {
            scene->removeEntity(currentMenu);
            currentMenu = nullptr;
        }
    }
};

Dynamic UI Updates

void updateHUD(int score, int lives, int health) {
    char buffer[32];

    snprintf(buffer, sizeof(buffer), "Score: %d", score);
    scoreLabel->setText(buffer);

    snprintf(buffer, sizeof(buffer), "Lives: %d", lives);
    livesLabel->setText(buffer);

    snprintf(buffer, sizeof(buffer), "Health: %d%%", health);
    healthLabel->setText(buffer);
}

Next Steps

Now that you understand the UI system, you've completed the core game development topics. Continue with: - Advanced Graphics - Advanced sprite techniques - Camera and Scrolling - Create scrolling levels - Performance Tuning - Improve performance


See also: - API Reference - UIElement - API Reference - UIButton - API Reference - UI Layouts - Manual - UI Overview