Listboxes: Containers & Custom Visual Wrappers¶
ListBox is a ready-to-use container designed for heterogeneous collections of interactive elements. Unlike custom implementations based on LayoutBase, ListBox provides fully encapsulated navigation, windowing, and clipping behavior out of the box, requiring no subclassing to alter control logic.
Elements inside the list retain their individual key bindings and value modifiers, while navigation, viewport calculations, and focus tracking are automatically managed by the container.
For convenience, two primary typedefs are provided based on the windowing strategy:
using ScrollListbox = ListBox<ScrollWindowAlgorithm>; ///< Scrollable list box (line-by-line scrolling)
using FlipListbox = ListBox<FlipWindowAlgorithm>; ///< Flip-based list box (page-by-page paging)
Technical Overview¶
Windowing Strategies: Scroll vs. Flip¶
ScrollListbox(ScrollWindowAlgorithm): Incremental scrolling that shifts focus item-by-item, smoothly advancing the viewport when reaching boundary edges.FlipListbox(FlipWindowAlgorithm): Page-based traversal that flips entire visible blocks at once when navigating past the viewport boundary, ideal for compact screens and rapid paging.
Construction and Pointer Arrays¶
ListBox accepts a row height parameter and an array of pointers to ElementBase instances (ElementBase* items[]) in its constructor.
- Heterogeneous Composition: Elements inside the list do not need to be of the same type. Any control deriving from
ElementBase(including elements wrapped inKeyModifier,LabeledLeftF,Padding, orPercent) can be passed into the container. - Row Height Alignment: The integer height parameter defines the fixed vertical span allocated to each item within the viewport.
- Focus Propagation: Focus state is forwarded directly to the active, visible child element. This ensures seamless event routing (e.g.,
KEY_LEFT/KEY_RIGHTfor value changes) while the container itself intercepts vertical events (KEY_UP/KEY_DOWN) for scrolling or flipping.
Visual Customization Pattern¶
ListBox is designed as a closed component regarding its navigation logic—there is no need to override it. However, visual decoration (such as custom scrollbars, outer paddings, or borders) is easily achieved through composition via template wrappers (e.g., Percent<T>) or by creating a custom wrapper that invokes ListBox::drawer() within a reduced bounding box.
Pattern 1: Standard Listbox Container¶
In standard forms or menus, ScrollListbox (or FlipListbox) is wrapped in a KeyModifier to intercept vertical navigation keys and automatically manage scrolling or page flipping.
#include <evabScrollListbox.h>
#include <evabKeyModifier.h>
#include <evabLabeled.h>
#include <evabInputInt.h>
#include <evabLayoutBase.h>
class StandardMenu : public KeyModifier<LayoutBase, KEY_DOWN, KEY_UP> {
// Elements with individual key handlers and labels
KeyModifier<LabeledLeftF<InputInt>, KEY_LEFT, KEY_RIGHT> mSpeed {F("Speed"), 13};
KeyModifier<LabeledLeftF<InputInt>, KEY_LEFT, KEY_RIGHT> mCourse{F("Course"), 37};
KeyModifier<LabeledLeftF<InputInt>, KEY_LEFT, KEY_RIGHT> mFuel {F("Fuel"), 95};
// Array of element pointers to initialize the list
ElementBase* mItems[3] = {&mSpeed, &mCourse, &mFuel};
// ScrollListbox with row height 2, capturing UP/DOWN for navigation
Focusable<KeyModifier<ScrollListbox, KEY_UP, KEY_DOWN>> mListbox{this, 2, mItems};
public:
void drawer(Screen* aScreen, Coor aPos, Coor aSize, unsigned char aIsFocused) override {
mListbox.Draw(aScreen, aPos, aSize, aIsFocused && IsFocused(&mListbox));
}
};
Pattern 2: Custom Decorated Listbox (Scrollbar Integration)¶
To add custom visual elements (such as a vertical scrollbar), create a wrapper class deriving from a layout decorator (e.g., Percent<ScrollListbox>) or ScrollListbox itself. The wrapper adjusts the coordinates passed to ScrollListbox::drawer() and renders auxiliary elements in the remaining area.
#include <evabScrollListbox.h>
#include <evabPercent.h>
#include <evabVerticalScrollBarPx.h>
// Custom Listbox with a vertical scrollbar on the right
class CustomListbox : public Percent<ScrollListbox> {
public:
using Percent<ScrollListbox>::Percent;
void drawer(Screen* aScreen, Coor aPos, Coor aSize, unsigned char aIsFocused) override {
// 1. Render the base ScrollListbox in a narrowed viewport (reserving 3 pixels on the right)
ScrollListbox::drawer(aScreen, {aPos.X + 1, aPos.Y}, {aSize.X - 3, aSize.Y}, aIsFocused);
// 2. Render a vertical scrollbar in the reserved right margin
VerticalScrollBarPx scrollbar(100 - GetPercent());
scrollbar.Draw(aScreen, {aPos.X + aSize.X - 1, aPos.Y}, {1, aSize.Y}, 0);
}
};
Usage Example in a Container¶
#include <evabLayoutBase.h>
#include <evabKeyModifier.h>
#include <evabLabeled.h>
#include <evabInputInt.h>
#include <evabHorizontalProgressBarPx.h>
class DecoratedMenuApp : public KeyModifier<LayoutBase, KEY_DOWN, KEY_UP> {
KeyModifier<LabeledLeftF<InputInt>, KEY_LEFT, KEY_RIGHT> mItem0{F("Speed"), 13};
KeyModifier<LabeledLeftF<InputInt>, KEY_LEFT, KEY_RIGHT> mItem1{F("Course"), 37};
KeyModifier<LabeledLeftF<HorizontalProgressBarPx>, KEY_LEFT, KEY_RIGHT> mItem2{F("Delay"), 30};
ElementBase* mItems[3] = {&mItem0, &mItem1, &mItem2};
// Listbox with custom drawing and a scrollbar
Focusable<KeyModifier<CustomListbox, KEY_UP, KEY_DOWN>> mCustomListbox{this, 2, mItems};
public:
void drawer(Screen* aScreen, Coor aPos, Coor aSize, unsigned char aIsFocused) override {
aScreen->TextCenter(aPos, {aSize.X, 1}, F("SETTINGS"), 0);
mCustomListbox.Draw(aScreen, {aPos.X, aPos.Y + 2}, {aSize.X, aSize.Y - 2}, aIsFocused && IsFocused(&mCustomListbox));
}
};
Execution Characteristics¶
- Independent Navigation and Editing: Navigation keys (
KEY_UP/KEY_DOWN) shift focus through items viaKeyModifier<CustomListbox>, whileKEY_LEFT/KEY_RIGHTare passed down to the focused row item to modify values. - State Integrity: Child elements inside
ListBoxthat fall outside the visible viewport area are automatically frozen by runtime checks inElementBase, preventing unnecessary redraw operations.