Element Composition: Focusable & LayoutBase¶
Interactive layout in EVA Boxy is structured around two core abstractions: Focusable (manages focus order and navigation state) and LayoutBase (provides layout coordination, event routing, and rendering contract).
Components are declared directly as class members. The declaration order defines the sequential focus navigation ring.
Technical Overview¶
Inheritance and Rendering Contract¶
LayoutBase is designed as an abstract base class intended strictly for subclassing (directly or in combination with one of its template wrappers like KeyModifier). When implementing a custom view, form, or screen manager, you derive from LayoutBase or a wrapper class and implement the pure virtual drawer() method.
The implementation of drawer() must adhere to one strict rule:
- Complete Area Coverage: The implementation must repaint or clear the entire screen area defined by the
aPosandaSizeparameters passed todrawer(). Leaving unrendered regions causes visual artifacts during redrawing.
To simplify layout management and meet this requirement, the Grid helper class can be used to slice aSize into region-aligned bounding boxes and automatically clear unallocated space via Clear().
Focusable¶
Focusable<T> is a template wrapper that integrates a UI control or modifier into the focus ring of a container.
- Registration: Registers the encapsulated instance
Twith the parentLayoutBaseupon construction. - Navigation Linkage: Maintained via an internal doubly linked list (
Next(),Prev()) initialized without dynamic memory allocations. - Passthrough Delegation: Routes rendering calls, state operations, and freeze notifications directly to the inner type
T.
LayoutBase & Implicit Interface¶
LayoutBase tracks the active Focusable node and manages event propagation. Interaction between LayoutBase and target controls relies on C++ duck typing. Controls implement member functions expected by their active modifiers or handlers, such as:
* Increment() — Value modification (numeric adjustments, option cycling).
Pattern 1: Single Form Element Focus¶
In a standard single-screen layout, focus transitions sequentially between individual fields via keyboard events.
#include <evabLayoutBase.h>
#include <evabFocusable.h>
#include <evabKeyModifier.h>
#include <evabKeyReactor.h>
#include <evabKeyCatcher.h>
#include <evabInputInt.h>
#include <evabInputButton.h>
#include <evabBoxyRest.h>
// Derive from KeyModifier (which inherits from LayoutBase)
class FormExample : public KeyModifier<LayoutBase, KEY_DOWN, KEY_UP> {
// Focus sequence: [1] mField1 -> [2] mField2 -> [3] mSaveBtn
Focusable<KeyReactor<InputInt, KEY_LEFT, KEY_RIGHT>> mField1 {this, 10};
Focusable<KeyReactor<InputInt, KEY_LEFT, KEY_RIGHT>> mField2 {this, 20};
Focusable<KeyCatcher<InputButton, KEY_ENTER>> mSaveBtn{this, &onSave, F("Save")};
Handler<FormExample> onSave {this, &FormExample::onSave};
void onSave(void*, CallbackInfo) { /* Execution logic */ }
public:
void drawer(Screen* aScreen, Coor aPos, Coor aSize, unsigned char aIsFocused) override {
// Grid covers and manages the total allocated area
Grid rest(aScreen, aPos, aSize);
rest.CutRow(1).Draw(&mField1, aIsFocused && IsFocused(&mField1));
rest.CutRow(1).Draw(&mField2, aIsFocused && IsFocused(&mField2));
rest.CutRow(1).Draw(&mSaveBtn, aIsFocused && IsFocused(&mSaveBtn));
// Clear remaining unallocated pixels in the area
rest.Clear();
}
};
Event Handling Behavior¶
KEY_UP/KEY_DOWN: Captured byKeyModifier<LayoutBase>to shift focus betweenFocusablenodes.KEY_LEFT/KEY_RIGHT: Passed to the currently focused node, triggering its internalKeyReactorto invokeIncrement.
Pattern 2: Sub-screen (LayoutBase) Paging¶
When managing multi-screen flows or tabbed views, Focusable wraps entire LayoutBase instances. Focus transitions switch the active sub-screen.
(Note: Internal frame lifecycle and state freezing are automatically managed by ElementBase upon draw call dispatching).
#include <evabLayoutBase.h>
#include <evabFocusable.h>
#include <evabKeyModifier.h>
#include <evabBoxyRest.h>
#include "ScreenStatus.h"
#include "ScreenSettings.h"
#include "ScreenInfo.h"
class PagedApp : public KeyModifier<LayoutBase, KEY_PAGEDOWN, KEY_PAGEUP> {
// Focus sequence across full LayoutBase instances
Focusable<ScreenStatus> mStatusScreen {this};
Focusable<ScreenSettings> mSettingsScreen {this};
Focusable<ScreenInfo> mInfoScreen {this};
public:
void drawer(Screen* aScreen, Coor aPos, Coor aSize, unsigned char aIsFocused) override {
// Draw only the active sub-screen
if (IsFocused(&mStatusScreen)) {
mStatusScreen.Draw(aScreen, aPos, aSize, aIsFocused);
}
else if (IsFocused(&mSettingsScreen)) {
mSettingsScreen.Draw(aScreen, aPos, aSize, aIsFocused);
}
else if (IsFocused(&mInfoScreen)) {
mInfoScreen.Draw(aScreen, aPos, aSize, aIsFocused);
}
}
};
Execution Characteristics¶
- Each sub-screen retains its own internal
Focusablering and key bindings. - Outer key events (
KEY_PAGEDOWN/KEY_PAGEUP) shift focus at the container level without interfering with inner sub-screen key mapping.