UI System Guide¶
Overview¶
PixelRoot32 provides a built-in UI framework for creating menus, HUDs (Heads-Up Displays), dialogs, and other interface elements. The UI system is designed to work seamlessly with the engine's rendering pipeline and is optimized for both ESP32 and PC targets.
Architecture¶
The UI system consists of:
- UI Elements: Individual components (buttons, labels, progress bars)
- Layouts: Arrangement systems (absolute, grid, stack)
- Styling: Visual appearance (colors, fonts, borders)
- Event Handling: Input response (clicks, hovers, focus)
UI Elements¶
1. UILabel - Text Display¶
Basic text display with optional styling:
#include <ui/UILabel.h>
auto label = std::make_unique<pixelroot32::ui::UILabel>(
10, 20, // x, y position
"Score: 0", // text
pixelroot32::graphics::Color::White, // color
2 // scale (2x font size)
);
scene.addEntity(label.get());
Properties: - Text content - Color - Font scale (1x, 2x, 3x) - Alignment (left, center, right)
2. UIButton - Interactive Button¶
Clickable button with visual feedback:
#include <ui/UIButton.h>
auto button = std::make_unique<pixelroot32::ui::UIButton>(
50, 100, // x, y
80, 24, // width, height
"Start Game" // label
);
// Set callback
button->onClick = []() {
engine.setScene(&gameScene);
};
// Styling
button->setBackgroundColor(pixelroot32::graphics::Color::Blue);
button->setTextColor(pixelroot32::graphics::Color::White);
button->setBorder(2, pixelroot32::graphics::Color::White);
scene.addEntity(button.get());
States: - Normal - Hover (PC only - no touch on ESP32) - Pressed - Disabled
3. UIProgressBar - Progress Indicator¶
Horizontal or vertical progress bar:
#include <ui/UIProgressBar.h>
// Health bar
auto healthBar = std::make_unique<pixelroot32::ui::UIProgressBar>(
10, 10, // x, y
100, 8, // width, height
pixelroot32::ui::UIProgressBar::HORIZONTAL // orientation
);
healthBar->setRange(0, 100); // min, max
healthBar->setValue(75); // current value
healthBar->setForegroundColor(pixelroot32::graphics::Color::Red);
healthBar->setBackgroundColor(pixelroot32::graphics::Color::DarkGray);
scene.addEntity(healthBar.get());
Use Cases: - Health bars - Loading progress - Experience/level progress - Cooldown timers
4. UIPanel - Container¶
Container for grouping elements:
#include <ui/UIPanel.h>
auto panel = std::make_unique<pixelroot32::ui::UIPanel>(
20, 20, // x, y
200, 150 // width, height
);
panel->setBackgroundColor(pixelroot32::graphics::Color::DarkBlue);
panel->setBorder(1, pixelroot32::graphics::Color::White);
// Add child elements
auto label = std::make_unique<pixelroot32::ui::UILabel>(10, 10, "Settings", Color::White, 2);
panel->addChild(label.get());
scene.addEntity(panel.get());
Layout Systems¶
Absolute Layout (Default)¶
Direct positioning with x, y coordinates:
// Place elements at specific positions
label->position.x = 10;
label->position.y = 20;
// Best for: HUDs, simple menus, absolute positioning
Grid Layout¶
Arrange elements in rows and columns:
#include <ui/layouts/UIGridLayout.h>
pixelroot32::ui::UIGridLayout grid(3, 2); // 3 columns, 2 rows
grid.setCellSize(70, 40);
grid.setSpacing(10, 10);
// Add elements - automatically positioned
for (int i = 0; i < 6; i++) {
auto btn = std::make_unique<UIButton>(0, 0, 60, 30, "Btn " + std::to_string(i));
grid.addElement(btn.get());
}
Use Cases: - Inventory grids - Level selection screens - Button matrices
Stack Layout¶
Vertical or horizontal stacking:
#include <ui/layouts/UIStackLayout.h>
// Vertical stack (menu)
pixelroot32::ui::UIStackLayout vStack(pixelroot32::ui::StackDirection::VERTICAL);
vStack.setSpacing(8);
auto startBtn = std::make_unique<UIButton>(0, 0, 120, 24, "Start");
auto optionsBtn = std::make_unique<UIButton>(0, 0, 120, 24, "Options");
auto quitBtn = std::make_unique<UIButton>(0, 0, 120, 24, "Quit");
vStack.addElement(startBtn.get());
vStack.addElement(optionsBtn.get());
vStack.addElement(quitBtn.get());
// Position the entire stack
vStack.setPosition(60, 80);
Use Cases: - Vertical menus - Horizontal toolbars - Form layouts
Styling¶
Colors¶
Use the engine's color palette:
using Color = pixelroot32::graphics::Color;
// Built-in colors
Color::Black, Color::White, Color::Red, Color::Green, Color::Blue
Color::Yellow, Color::Cyan, Color::Magenta
Color::DarkGray, Color::LightGray
Color::Orange, Color::Purple, Color::Brown
// Custom colors (RGB565)
Color customColor(0xF800); // Pure red in RGB565
Fonts¶
UI elements use the engine's font system:
// Default: 5x7 bitmap font
// Scale 1 = 5x7 pixels
// Scale 2 = 10x14 pixels
// Scale 3 = 15x21 pixels
label->setFontScale(2); // Double size text
Borders¶
Add borders to panels and buttons:
panel->setBorder(
2, // border width (pixels)
pixelroot32::graphics::Color::White // border color
);
// Rounded corners (if supported)
button->setCornerRadius(4); // 4 pixel radius
Input Handling¶
Button Navigation¶
For menu navigation with D-pad:
class MenuScene : public Scene {
std::vector<UIButton*> menuButtons;
int selectedIndex = 0;
public:
void init() override {
// Create buttons
auto startBtn = std::make_unique<UIButton>(50, 60, 100, 20, "Start");
auto optionsBtn = std::make_unique<UIButton>(50, 90, 100, 20, "Options");
auto quitBtn = std::make_unique<UIButton>(50, 120, 100, 20, "Quit");
menuButtons = {startBtn.get(), optionsBtn.get(), quitBtn.get()};
// Add to scene
addEntity(startBtn.get());
addEntity(optionsBtn.get());
addEntity(quitBtn.get());
// Store ownership
ownedButtons.push_back(std::move(startBtn));
ownedButtons.push_back(std::move(optionsBtn));
ownedButtons.push_back(std::move(quitBtn));
// Highlight first button
updateSelection();
}
void update(unsigned long deltaTime) override {
auto& input = engine.getInputManager();
// Navigate with UP/DOWN
if (input.isButtonJustPressed(1)) { // DOWN
selectedIndex = (selectedIndex + 1) % menuButtons.size();
updateSelection();
}
if (input.isButtonJustPressed(0)) { // UP
selectedIndex = (selectedIndex - 1 + menuButtons.size()) % menuButtons.size();
updateSelection();
}
// Activate with A button
if (input.isButtonJustPressed(4)) { // A
menuButtons[selectedIndex]->triggerClick();
}
}
void updateSelection() {
for (int i = 0; i < menuButtons.size(); i++) {
if (i == selectedIndex) {
menuButtons[i]->setBackgroundColor(Color::Yellow);
menuButtons[i]->setTextColor(Color::Black);
} else {
menuButtons[i]->setBackgroundColor(Color::Blue);
menuButtons[i]->setTextColor(Color::White);
}
}
}
private:
std::vector<std::unique_ptr<UIButton>> ownedButtons;
};
Direct Touch/Mouse (PC)¶
On PC with mouse:
// Check if mouse is over element (PC only)
bool isMouseOver = button->containsPoint(mouseX, mouseY);
// Button handles this automatically on PC
// On ESP32, use D-pad navigation as shown above
Common UI Patterns¶
1. Main Menu¶
class MainMenuScene : public Scene {
public:
void init() override {
// Title
auto title = std::make_unique<UILabel>(60, 30, "MY GAME", Color::Yellow, 3);
addEntity(title.get());
ownedUI.push_back(std::move(title));
// Menu buttons using stack layout
pixelroot32::ui::UIStackLayout menuLayout(StackDirection::VERTICAL);
menuLayout.setPosition(80, 80);
menuLayout.setSpacing(12);
auto startBtn = createButton("Start Game", [&]() {
engine.setScene(&gameScene);
});
auto optionsBtn = createButton("Options", [&]() {
engine.setScene(&optionsScene);
});
auto quitBtn = createButton("Quit", [&]() {
// Handle quit
});
menuLayout.addElement(startBtn.get());
menuLayout.addElement(optionsBtn.get());
menuLayout.addElement(quitBtn.get());
}
private:
std::vector<std::unique_ptr<UIElement>> ownedUI;
};
2. In-Game HUD¶
class GameHUD : public Scene {
UILabel* scoreLabel;
UIProgressBar* healthBar;
public:
void init() override {
// Score (top-left)
auto score = std::make_unique<UILabel>(5, 5, "Score: 0", Color::White, 1);
scoreLabel = score.get();
addEntity(score.get());
// Health bar (top-right)
auto health = std::make_unique<UIProgressBar>(180, 5, 55, 6);
healthBar = health.get();
healthBar->setRange(0, 100);
healthBar->setValue(100);
healthBar->setForegroundColor(Color::Red);
addEntity(health.get());
// Store ownership
ownedUI.push_back(std::move(score));
ownedUI.push_back(std::move(health));
}
void setScore(int score) {
scoreLabel->setText("Score: " + std::to_string(score));
}
void setHealth(int health) {
healthBar->setValue(health);
}
private:
std::vector<std::unique_ptr<UIElement>> ownedUI;
};
3. Dialog/Modal¶
class PauseDialog : public UIPanel {
public:
PauseDialog() : UIPanel(40, 60, 160, 80) {
setBackgroundColor(Color::DarkBlue);
setBorder(2, Color::White);
// Title
auto title = std::make_unique<UILabel>(50, 70, "PAUSED", Color::Yellow, 2);
addChild(title.get());
// Resume button
auto resumeBtn = std::make_unique<UIButton>(70, 100, 100, 20, "Resume");
resumeBtn->onClick = [&]() {
close();
};
addChild(resumeBtn.get());
// Store children
children.push_back(std::move(title));
children.push_back(std::move(resumeBtn));
}
void close() {
visible = false;
// Or remove from scene
}
private:
std::vector<std::unique_ptr<UIElement>> children;
};
Performance Tips¶
1. Batch UI Updates¶
Don't update every frame unless necessary:
// ✅ GOOD: Update only when score changes
void onScoreChanged(int newScore) {
scoreLabel->setText("Score: " + std::to_string(newScore));
}
// ❌ BAD: Updating every frame
void update(unsigned long deltaTime) override {
scoreLabel->setText("Score: " + std::to_string(score)); // Unnecessary!
}
2. Minimize UI Elements¶
ESP32 has limited resources:
// Recommended limits for ESP32:
// - Total UI elements: < 20 per scene
// - Labels: Use font scale instead of many labels
// - Panels: Reuse single panel for multiple screens
3. Static vs Dynamic UI¶
// Static UI (created once)
void init() override {
// Create all UI elements here
}
// Dynamic UI (created on demand)
void showInventory() {
// Create inventory UI only when opened
// Destroy when closed to free memory
}
Troubleshooting¶
UI Not Rendering¶
-
Check render layer: UI should be on top layer (layer 2)
-
Verify position: Ensure element is within screen bounds
-
Add to scene: Don't forget
scene.addEntity()
Button Clicks Not Working¶
- Input focus: Ensure scene is active
- Button state: Check if button is enabled
- Callback binding: Verify lambda captures are valid
Text Not Displaying¶
- Font scale: Ensure scale is reasonable (1-3)
- Color contrast: Text color vs background
- String content: Check for empty strings
API Reference¶
- UIElement - Base class
- UILabel - Text display
- UIButton - Interactive button
- UIProgressBar - Progress bar
- UIPanel - Container
Examples¶
- Main Menu: Simple button navigation
- In-Game HUD: Score, health, minimap
- Options Screen: Sliders, checkboxes
- Inventory: Grid layout, item selection
See also:
- Input System Guide - Button navigation
- API Reference - UI
- Examples - Menu