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!
Navigation¶
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¶
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.
Navigation¶
- 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¶
Menu System¶
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