CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
ListView.cpp
Go to the documentation of this file.
1
7
12#include "cdc_ui/ViewStack.h"
13#include "cdc_ui/I18n.h"
14#include "cdc_hal/IDisplay.h"
15#include "cdc_core/Raii.h"
16#include "cdc_log.h"
17#include <goodisplay/gdey029T94.h>
18
19static const char* TAG = "ListView";
20
27static constexpr int TITLE_Y = 5;
28static constexpr int LIST_START_Y = 30;
29static constexpr int ITEM_PADDING_X = 10;
31
38static constexpr uint8_t VISIBLE_ITEMS = 4;
39
40namespace cdc::ui {
41
49void ListView::init(const char* title, const ListItem* items, uint16_t count) {
50 title_ = title;
51 items_ = items;
52 itemCount_ = count > MAX_ITEMS ? MAX_ITEMS : count;
53
54 // Only reset position if not preserving (for back-navigation)
55 if (!preservePosition_) {
56 selection_ = 0;
57 scrollPos_ = 0;
58 } else {
59 preservePosition_ = false;
60 if (selection_ >= itemCount_) {
61 selection_ = itemCount_ > 0 ? itemCount_ - 1 : 0;
62 }
63 ensureVisible();
64 }
65 visibleItems_ = VISIBLE_ITEMS;
66 dirty_ = true;
67
68 LOG_D(TAG, "init: title='%s', items=%d, visible=%d", title, itemCount_, visibleItems_);
69}
70
76void ListView::setSelection(uint16_t index) {
77 if (index < itemCount_ && index != selection_) {
78 selection_ = index;
79 ensureVisible();
80 dirty_ = true;
81 }
82}
83
89 if (items_ && selection_ < itemCount_) {
90 return &items_[selection_];
91 }
92 return nullptr;
93}
94
100void ListView::navigate(bool down) {
101 if (itemCount_ == 0) return;
102
103 if (down) {
104 if (selection_ < itemCount_ - 1) {
105 selection_++;
106 } else {
107 // Wrap-around: bottom to top
108 selection_ = 0;
109 scrollPos_ = 0;
110 }
111 } else {
112 if (selection_ > 0) {
113 selection_--;
114 } else {
115 // Wrap-around: top to bottom
116 selection_ = itemCount_ - 1;
117 }
118 }
119
120 ensureVisible();
121 dirty_ = true;
122
123 LOG_D(TAG, "navigate: sel=%d, scroll=%d", selection_, scrollPos_);
124}
125
130void ListView::ensureVisible() {
131 if (selection_ >= scrollPos_ + visibleItems_) {
132 scrollPos_ = selection_ - visibleItems_ + 1;
133 }
134 if (selection_ < scrollPos_) {
135 scrollPos_ = selection_;
136 }
137}
138
145 switch (key) {
146 case KEY_UP: {
147 cdc::core::RecursiveMutexGuard guard(editMutex_);
148 navigate(false);
150 }
151
152 case KEY_DOWN: {
153 cdc::core::RecursiveMutexGuard guard(editMutex_);
154 navigate(true);
156 }
157
158 case KEY_YES: { // Select
159 // Snapshot the target under the lock; the callback may re-enter the
160 // call stack (plugin dispatch) and must not run while editMutex_ is
161 // held, so release before invoking it.
162 void* userData = nullptr;
163 uint16_t sel = 0;
164 bool valid = false;
165 {
166 cdc::core::RecursiveMutexGuard guard(editMutex_);
167 if (onSelect_ && items_ && selection_ < itemCount_) {
168 sel = selection_;
169 userData = items_[selection_].userData;
170 valid = true;
171 }
172 }
173 if (valid) {
174 onSelect_(sel, userData);
176 }
177 // No select callback bound: leave KEY_YES free for the plugin
178 // (delivered via the KEY_PRESSED EventBus) instead of swallowing it.
180 }
181
182 case KEY_MENU: { // Context menu
183 if (!onMenu_) return InputResult::IGNORED;
184 void* userData = nullptr;
185 uint16_t sel = 0xFFFF; // sentinel: empty list / no selection
186 {
187 cdc::core::RecursiveMutexGuard guard(editMutex_);
188 if (items_ && selection_ < itemCount_) {
189 sel = selection_;
190 userData = items_[selection_].userData;
191 }
192 }
193 onMenu_(sel, userData);
195 }
196
197 case KEY_NO: // Back
199
200 default:
202 }
203}
204
206 cdc::core::RecursiveMutexGuard guard(editMutex_);
207 if (itemCount_ == 0) return InputResult::IGNORED;
208 switch (key) {
210 case KEY_DOWN: setSelection(itemCount_ - 1); return InputResult::CONSUMED;
211 default: return InputResult::IGNORED;
212 }
213}
214
219const char* ListView::getFooterHint() const {
220 if (customFooter_) {
221 return customFooter_;
222 }
223 if (customHint_) {
224 return customHint_;
225 }
226 return ui::tr("core.hint_ok_back");
227}
228
234void ListView::render(bool partial) {
236 if (!display) return;
237
238 auto* gfx = static_cast<Gdey029T94*>(display->getNativeHandle());
239 if (!gfx) return;
240
241 // Serialise against a cross-task writer that may re-point items_ / free the
242 // backing buffer mid-render (plugin list edits). No-op when unset.
243 cdc::core::RecursiveMutexGuard guard(editMutex_);
244
245 const uint16_t width = display->getWidth();
246 const uint16_t height = display->getHeight();
247
248 if (!partial) {
249 gfx->fillScreen(EPD_WHITE);
250 }
251
252 gfx->setTextWrap(false); // font/size/color reset centrally in ViewStack::render
253
254 // Title + underline
255 render::drawHeaderLeft(gfx, title_, ITEM_PADDING_X, TITLE_Y, width);
256
257 // Items
258 const int rowWidth = width - SCROLL_INDICATOR_WIDTH;
259 for (uint8_t i = 0; i < visibleItems_; i++) {
260 uint16_t itemIndex = scrollPos_ + i;
261 int y = LIST_START_Y + i * itemHeight_;
262 drawRow(gfx, itemIndex, y, rowWidth);
263 }
264
265 // Empty placeholder (after item rects so it's not overpainted)
266 if (itemCount_ == 0 && emptyText_) {
267 int16_t x1, y1;
268 uint16_t w, h;
269 gfx->setTextColor(EPD_BLACK);
270 gfx->setTextSize(1);
271 gfx->getTextBounds(emptyText_, 0, 0, &x1, &y1, &w, &h);
272 int x = (width - static_cast<int>(w)) / 2;
273 int y = LIST_START_Y + (visibleItems_ * itemHeight_) / 2 - h / 2;
274 gfx->setCursor(x < 0 ? 0 : x, y);
275 render::printText(gfx, emptyText_);
276 }
277
278 // Scroll indicators
279 if (itemCount_ > visibleItems_) {
280 int indicatorX = width - SCROLL_INDICATOR_WIDTH;
281 int listHeight = visibleItems_ * itemHeight_;
282 render::drawScrollIndicator(gfx, indicatorX, LIST_START_Y, listHeight,
283 itemCount_, visibleItems_, scrollPos_);
284 }
285
286 // Footer with position counter
287 char positionStr[16];
288 const char* prefix = nullptr;
289 if (itemCount_ > 0) {
290 snprintf(positionStr, sizeof(positionStr), "%u/%u ", selection_ + 1, itemCount_);
291 prefix = positionStr;
292 }
293 const char* hint = getFooterHint();
294 render::drawFooterBar(gfx, width, height, prefix, hint, true);
295
296 dirty_ = false;
297}
298
307void ListView::drawRow(Gdey029T94* gfx, uint16_t itemIndex, int y, int rowWidth) {
308 gfx->setFont(nullptr);
309 gfx->setTextSize(1);
310 gfx->setTextWrap(false);
311
312 // Clear item area
313 gfx->fillRect(0, y, rowWidth, itemHeight_, EPD_WHITE);
314
315 if (itemIndex >= itemCount_) return;
316
317 const ListItem& item = items_[itemIndex];
318 bool isSelected = (itemIndex == selection_);
319
320 if (isSelected) {
321 gfx->fillRect(2, y + 1, rowWidth - 4, itemHeight_ - 2, EPD_BLACK);
322 gfx->setTextColor(EPD_WHITE);
323 } else {
324 gfx->setTextColor(EPD_BLACK);
325 }
326
327 bool handled = false;
328 if (itemRenderer_) {
329 handled = itemRenderer_(gfx, item, itemIndex,
330 0, y, rowWidth, itemHeight_,
331 isSelected, itemRendererCtx_);
332 }
333
334 if (!handled) {
335 int textX = ITEM_PADDING_X;
336 if (item.icon) {
337 char iconStr[2] = {static_cast<char>(item.icon), '\0'};
338 gfx->setCursor(textX, y + 4);
339 render::printText(gfx, iconStr);
340 if (item.iconDisabled) {
341 uint16_t color = isSelected ? EPD_WHITE : EPD_BLACK;
342 gfx->drawLine(textX, y + 10, textX + 6, y + 10, color);
343 }
344 textX += 10;
345 } else {
346 uint16_t bullet_color = isSelected ? EPD_WHITE : EPD_BLACK;
347 gfx->fillCircle(textX + 2, y + itemHeight_ / 2, 2, bullet_color);
348 textX += 9;
349 }
350
351 gfx->setCursor(textX, y + 4);
352 if (item.label) {
353 render::printTruncated(gfx, item.label, rowWidth - textX - 2);
354 }
355 }
356}
357
367void ListView::updateItem(uint16_t index) {
368 cdc::core::RecursiveMutexGuard guard(editMutex_);
369 if (!items_ || index >= itemCount_) return;
370 markDirty();
371}
372
380
386void ListView::insertItem(uint16_t index) {
387 cdc::core::RecursiveMutexGuard guard(editMutex_);
388 if (!items_ || itemCount_ >= MAX_ITEMS) return;
389 if (index > itemCount_) index = itemCount_;
390 uint16_t oldCount = itemCount_;
391 itemCount_++;
392 // Keep the selected item selected: it shifts down when inserting at or
393 // before it.
394 if (oldCount > 0 && index <= selection_ && selection_ + 1 < itemCount_) {
395 selection_++;
396 }
397 ensureVisible();
398 markDirty();
399}
400
406void ListView::removeItem(uint16_t index) {
407 cdc::core::RecursiveMutexGuard guard(editMutex_);
408 if (!items_ || itemCount_ == 0 || index >= itemCount_) return;
409 itemCount_--;
410 if (selection_ > index) {
411 selection_--;
412 }
413 if (selection_ >= itemCount_) {
414 selection_ = itemCount_ > 0 ? itemCount_ - 1 : 0;
415 }
416 ensureVisible();
417 markDirty();
418}
419
423
425
435ListView* showListView(const char* title, const ListItem* items, uint16_t count,
436 ListView::SelectCallback onSelect, const char* hint) {
437 s_sharedListView.init(title, items, count);
438 s_sharedListView.setOnSelect(onSelect);
439 if (hint) {
440 s_sharedListView.setHint(hint);
441 }
443 return &s_sharedListView;
444}
445
446} // namespace cdc::ui
static const char * TAG
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
constexpr int SCROLL_INDICATOR_WIDTH
Scroll indicator column width in pixels.
static constexpr uint8_t VISIBLE_ITEMS
Visible item count derived from available list area.
Definition ListView.cpp:38
static constexpr int LIST_START_Y
Definition ListView.cpp:28
static constexpr int ITEM_PADDING_X
Definition ListView.cpp:29
static constexpr int TITLE_Y
Display layout constants.
Shared RAII wrappers for firmware resources.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_D(tag, fmt,...)
Definition cdc_log.h:148
Scoped guard for a FreeRTOS recursive mutex.
Definition Raii.h:245
void repaintPartial()
Requests a redraw; the actual repaint happens once per render cycle.
Definition ListView.cpp:377
const char * getFooterHint() const override
Returns footer hint text for this list.
Definition ListView.cpp:219
void(*)(uint16_t index, void *userData) SelectCallback
Definition ListView.h:46
const ListItem * getSelectedItem() const
Returns the currently selected item.
Definition ListView.cpp:88
static constexpr uint16_t MAX_ITEMS
Definition ListView.h:36
InputResult onKey(char key) override
Handles key input for list navigation and actions.
Definition ListView.cpp:144
void insertItem(uint16_t index)
Reflects a caller-side insertion at index and marks dirty.
Definition ListView.cpp:386
void setSelection(uint16_t index)
Sets the selected item index.
Definition ListView.cpp:76
void render(bool partial) override
Renders list rows, selection, scroll indicators, and footer.
Definition ListView.cpp:234
InputResult onLongPress(char key) override
Definition ListView.cpp:205
void init(const char *title, const ListItem *items, uint16_t count)
Initializes list data and selection state.
Definition ListView.cpp:49
void removeItem(uint16_t index)
Reflects a caller-side removal at index and marks dirty.
Definition ListView.cpp:406
void updateItem(uint16_t index)
Marks the list dirty after the caller updated a backing item.
Definition ListView.cpp:367
void markDirty() override
Definition IView.h:197
const char * customFooter_
Definition IView.h:214
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:53
void push(IView *view, void *context=nullptr)
bool valid
IDisplay * getDisplayInstance()
Returns lazily created singleton display instance.
constexpr int SCROLL_INDICATOR_WIDTH
Scroll indicator column width in pixels.
void drawFooterBar(Gdey029T94 *gfx, uint16_t width, uint16_t height, const char *prefix, const char *hint, bool force=false)
Draws footer bar with optional prefix and hint text.
void drawHeaderLeft(Gdey029T94 *gfx, const char *title, int x, int y, uint16_t width, int underlineOffset=18)
void printTruncated(Adafruit_GFX *gfx, const char *text, int maxWidthPx)
Print text at the current cursor, truncated with an ellipsis to fit maxWidthPx. Caller must have alre...
void printText(Adafruit_GFX *gfx, const char *text)
Draws CP437 text with the built-in 6x8 glyph font, byte-for-byte.
void drawScrollIndicator(Gdey029T94 *gfx, int x, int y, int listHeight, uint16_t totalItems, uint16_t visibleItems, uint16_t scrollPos)
Draws scroll arrows and scrollbar thumb.
Centralized key-code constants for cdc_views.
Definition IModule.h:8
const char * tr(const char *key)
Look up a translation by string key.
Definition I18n.h:209
static ListView s_sharedListView
Convenience factory/helper function.
Definition ListView.cpp:424
static constexpr char KEY_MENU
Open context menu / digit '3'.
Definition KeyCodes.h:47
static constexpr char KEY_DOWN
Move selection down (numeric '8').
Definition KeyCodes.h:35
Gdey029T94 * display
InputResult
Definition IView.h:11
static constexpr char KEY_NO
Cancel / Back / Backspace.
Definition KeyCodes.h:44
static constexpr int TITLE_Y
Layout constants mirror the ones used by T9InputView.
ListView * showListView(const char *title, const ListItem *items, uint16_t count, ListView::SelectCallback onSelect, const char *hint=nullptr)
Shows a shared list view instance with selection callback.
Definition ListView.cpp:435
static const char * TAG
static constexpr char KEY_UP
Move selection up (numeric '2').
Definition KeyCodes.h:32
static constexpr char KEY_YES
Confirm / OK / Save.
Definition KeyCodes.h:41
const char * label
Definition ListView.h:16