Widgets and Focus

A Widget is a drawable object inside an App, such as a text button, number selector, or line chart. Apps and Widgets belong to different layers: ViewManager manages Apps, while Widgets are usually App members and share their App’s lifetime.

An interactive button

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class ButtonApp final : public IApplication {
public:
ButtonApp(PixelUI& ui, void*)
: ui_(ui), button_(ui, 34, 24, 60, 16, "Select") {}

void onEnter(ExitCallback cb) override {
IApplication::onEnter(cb);
button_.setCallback([this]() {
selected_ = !selected_;
ui_.markDirty();
});
button_.onLoad();
ui_.addWidgetToFocusManager(&button_);
}

void draw() override {
button_.draw();
}

bool handleInput(InputEvent event) override {
if (event == InputEvent::BACK) {
requestExit();
return true;
}
return false;
}

private:
PixelUI& ui_;
TextButton button_;
bool selected_ = false;
};

FocusManager belongs to PixelUI; individual Apps no longer create one. ui.handleInput() first lets FocusManager process LEFT, RIGHT, and SELECT, then passes unconsumed events to the current App.

Focusable state

IWidget defaults to focusable = false. TextButton, IconButton, Label, and NumScroll make themselves focusable; Clock is display-only. Explicitly enable focus when a Brace, Histogram, or CurveChart must participate in navigation:

1
2
chart_.setFocusable(true);
ui_.addWidgetToFocusManager(&chart_);

FocusManager navigates only nodes that are focusable, visible, and enabled, and whose ancestors are also visible and enabled.

Widget parent-child tree

The Widget tree allocates no nodes and does not own child objects. It creates intrusive parent-child links between existing Widgets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class PanelApp final : public IApplication {
public:
PanelApp(PixelUI& ui, void*)
: ui_(ui), panel_(ui, 16, 20, 96, 36),
left_(ui, 7, 10, 38, 16, "Left"),
right_(ui, 51, 10, 38, 16, "Right") {
panel_.addChild(left_);
panel_.addChild(right_);
}

void onEnter(ExitCallback cb) override {
IApplication::onEnter(cb);
panel_.onLoad();
left_.onLoad();
right_.onLoad();

// Register only the root. FocusManager traverses children with DFS.
ui_.addWidgetToFocusManager(&panel_);
}

void draw() override {
// Drawing the root recursively draws its children.
panel_.draw();
}

private:
PixelUI& ui_;
Brace panel_;
TextButton left_;
TextButton right_;
};

Child coordinates are local to the parent. Children move with the parent. By default, a parent’s bounds clip its children; call setClipChildren(false) to disable clipping.

Widgets are neither copyable nor movable. When a Widget is destroyed or detached from the tree, FocusManager is notified and clears current or active pointers to it.

Built-in Widget reference

Widget Purpose Main configuration
TextButton Text button setCallback, setText, setPosition, setSize
IconButton XBM icon button setSource, setCallback
Label Text with directional entrance Animation setText, setLoadPos, setCallback
NumScroll Integer selection setRange, setValue, getValue, setFixedIntDigits
Clock Analog clock setHour, setMinute, setSecond, setRadius
Brace Border or container setDrawContentFunction, addChild, setCallback
Histogram Bar history chart addData, window and history statistics
CurveChart Line history chart addData, window and history statistics

The callback type is PixelUI’s VoidCallback, not std::function. Stored const char* strings and XBM pointers are also non-owning; do not point them at arrays that have left scope.

TextButton and Label

TextButton Label
TextButton geometry Label slide-in direction

TextButton uses (x, y, width, height) for its rectangle. Label’s POS selects the direction of its entrance Animation.

NumScroll and Clock

NumScroll Clock
NumScroll geometry Analog clock geometry

NumScroll coordinates describe the rectangle’s top-left corner. Clock uses center (x, y) and radius. Clock is display-only by default and does not participate in Focus navigation.

Brace

Brace geometry

Brace can draw custom content with setDrawContentFunction() or serve as a parent in the Widget tree. Diagram coordinates and dimensions describe its local bounds.

Fixed Chart buffers

Histogram and CurveChart do not allocate sample buffers on the heap. The caller provides storage:

1
2
3
4
5
6
7
8
9
10
float samples_[76]{};

CurveChart chart_{
ui_,
69, 45, 56, 18,
samples_,
ChartExpandSize<76, 63>{},
EXPAND_BASE::BOTTOM_RIGHT,
"Curve"
};

The number of buffer elements must equal the expanded width. A template static_assert verifies this relationship at compile time. The buffer must outlive the Widget; declaring it before the Chart member gives the correct destruction order.

Histogram CurveChart
Histogram expansion geometry CurveChart expansion geometry

Both Charts use the same expansion geometry: EXPAND_BASE selects the fixed corner, and ChartExpandSize<W, H> sets expanded width and height. Histogram draws bars; CurveChart draws a continuous line.