CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
InfoView.cpp
Go to the documentation of this file.
1
6
11#include "cdc_ui/ViewStack.h"
12#include "cdc_ui/I18n.h"
13#include "cdc_hal/IDisplay.h"
14#include "cdc_log.h"
15#include <goodisplay/gdey029T94.h>
16#include <cstring>
17
18static const char* TAG = "InfoView";
19
26static constexpr int TITLE_Y = 5;
27static constexpr int TEXT_START_Y = 28;
28static constexpr int TEXT_MARGIN = 8;
31
32namespace cdc::ui {
33
40namespace {
41
42constexpr uint16_t WRAP_COLS = 44;
43
44void wrapInPlace(char* buf, uint16_t maxCols) {
45 if (!buf || maxCols == 0) return;
46 uint16_t col = 0;
47 char* lastSpace = nullptr;
48 for (char* p = buf; *p; ++p) {
49 if (*p == '\n') {
50 col = 0;
51 lastSpace = nullptr;
52 continue;
53 }
54 if (col >= maxCols && lastSpace) {
55 *lastSpace = '\n';
56 col = static_cast<uint16_t>(p - lastSpace - 1);
57 lastSpace = nullptr;
58 }
59 if (*p == ' ') lastSpace = p;
60 ++col;
61 }
62}
63
64} // namespace
65
66void InfoView::init(const char* title, const char* text) {
67 if (title) {
68 strncpy(titleBuf_, title, MAX_TITLE_LEN - 1);
69 titleBuf_[MAX_TITLE_LEN - 1] = '\0';
70 } else {
71 titleBuf_[0] = '\0';
72 }
73
74 if (!textBuf_) {
76 }
77 if (textBuf_) {
78 if (text) {
79 strncpy(textBuf_.get(), text, MAX_TEXT_LEN - 1);
80 textBuf_.get()[MAX_TEXT_LEN - 1] = '\0';
81 } else {
82 textBuf_.get()[0] = '\0';
83 }
84 wrapInPlace(textBuf_.get(), WRAP_COLS);
85 }
86
87 scrollLine_ = 0;
88 totalLines_ = countLines();
89 customHint_ = nullptr;
90 dirty_ = true;
91
92 LOG_D(TAG, "init: title='%s', lines=%d", titleBuf_, totalLines_);
93}
94
99uint16_t InfoView::countLines() const {
100 if (!textBuf_ || textBuf_.get()[0] == '\0') return 0;
101
102 uint16_t lines = 1;
103 const char* p = textBuf_.get();
104 while (*p) {
105 if (*p == '\n') lines++;
106 p++;
107 }
108 return lines;
109}
110
116void InfoView::scroll(bool down) {
117 if (totalLines_ <= VISIBLE_LINES) return;
118
119 if (down) {
120 if (scrollLine_ < totalLines_ - VISIBLE_LINES) {
121 scrollLine_++;
122 } else {
123 // Wrap to top
124 scrollLine_ = 0;
125 }
126 } else {
127 if (scrollLine_ > 0) {
128 scrollLine_--;
129 } else {
130 // Wrap to bottom
131 scrollLine_ = totalLines_ - VISIBLE_LINES;
132 }
133 }
134
135 dirty_ = true;
136}
137
144 if (key == KEY_YES && onYes_) {
145 onYes_(callbackUserData_);
147 }
148 if (key == KEY_NO && onNo_) {
149 onNo_(callbackUserData_);
151 }
152 if (key == KEY_MENU && onMenu_) {
153 onMenu_(menuUserData_);
155 }
156
157 switch (key) {
158 case KEY_UP:
159 scroll(false);
161
162 case KEY_DOWN:
163 scroll(true);
165
166 case KEY_NO: // Back
168
169 default:
170 // KEY_YES (and any other key) is left free: the plugin can bind it
171 // via the KEY_PRESSED EventBus or an explicit onYes_ callback.
173 }
174}
175
182 if (totalLines_ <= VISIBLE_LINES) return InputResult::IGNORED;
183 switch (key) {
184 case KEY_UP: scrollLine_ = 0; dirty_ = true; return InputResult::CONSUMED;
185 case KEY_DOWN: scrollLine_ = totalLines_ - VISIBLE_LINES; dirty_ = true; return InputResult::CONSUMED;
186 default: return InputResult::IGNORED;
187 }
188}
189
194const char* InfoView::getFooterHint() const {
195 if (customHint_) {
196 return customHint_;
197 }
198 return ui::tr("core.hint_scroll_back");
199}
200
206void InfoView::render(bool partial) {
208 if (!display) return;
209
210 auto* gfx = static_cast<Gdey029T94*>(display->getNativeHandle());
211 if (!gfx) return;
212
213 const uint16_t width = display->getWidth();
214 const uint16_t height = display->getHeight();
215
216 if (!partial) {
217 gfx->fillScreen(EPD_WHITE);
218 } else {
219 // Partial repaint (e.g. as a modal over a base view): clear the whole
220 // content band including the header, so a header/body underneath does
221 // not bleed through behind the freshly drawn title.
222 gfx->fillRect(0, 0, width, height - FOOTER_HEIGHT, EPD_WHITE);
223 }
224
225 gfx->setFont(nullptr); // 6x8 built-in (CP437): never inherit a leaked GFX font
226 gfx->setTextColor(EPD_BLACK);
227 gfx->setTextSize(1);
228
229 // Title + underline
230 const char* title = (titleBuf_[0] != '\0') ? titleBuf_ : nullptr;
231 render::drawHeaderLeft(gfx, title, TEXT_MARGIN, TITLE_Y, width);
232
233 // Text area dimensions
234 int textAreaWidth = width - TEXT_MARGIN * 2 - SCROLL_INDICATOR_WIDTH;
235 int textAreaHeight = height - TEXT_START_Y - FOOTER_HEIGHT;
236
237 // Clear text area
238 gfx->fillRect(TEXT_MARGIN, TEXT_START_Y, textAreaWidth, textAreaHeight, EPD_WHITE);
239
240 // Render visible lines
241 if (textBuf_ && textBuf_.get()[0] != '\0') {
242 const char* lineStart = textBuf_.get();
243 uint16_t currentLine = 0;
244 int y = TEXT_START_Y;
245
246 // Skip to scroll position
247 while (currentLine < scrollLine_ && *lineStart) {
248 if (*lineStart == '\n') currentLine++;
249 lineStart++;
250 }
251
252 // Render visible lines
253 for (uint8_t i = 0; i < VISIBLE_LINES && *lineStart; i++) {
254 gfx->setCursor(TEXT_MARGIN, y);
255
256 // Find end of line
257 const char* lineEnd = lineStart;
258 while (*lineEnd && *lineEnd != '\n') lineEnd++;
259
260 // Print line (character by character to handle no null-terminator)
261 while (lineStart < lineEnd) {
262 gfx->print(*lineStart);
263 lineStart++;
264 }
265
266 // Skip newline
267 if (*lineStart == '\n') lineStart++;
268
269 y += LINE_HEIGHT;
270 }
271 }
272
273 // Scroll indicators (if needed)
274 if (totalLines_ > VISIBLE_LINES) {
275 int indicatorX = width - SCROLL_INDICATOR_WIDTH;
276 int listHeight = VISIBLE_LINES * LINE_HEIGHT;
277 render::drawScrollIndicator(gfx, indicatorX, TEXT_START_Y, listHeight,
278 totalLines_, VISIBLE_LINES, scrollLine_);
279 }
280
281 // Footer
282 char posStr[20]; // Max: "65535-65535/65535 \0"
283 const char* prefix = nullptr;
284 if (totalLines_ > VISIBLE_LINES) {
285 snprintf(posStr, sizeof(posStr), "%u-%u/%u ",
286 scrollLine_ + 1,
287 scrollLine_ + VISIBLE_LINES > totalLines_ ? totalLines_ : scrollLine_ + VISIBLE_LINES,
288 totalLines_);
289 prefix = posStr;
290 }
291 const char* hint = getFooterHint();
292 render::drawFooterBar(gfx, width, height, prefix, hint, true);
293
294 dirty_ = false;
295}
296
300
302
310InfoView* showInfo(const char* title, const char* text, const char* hint) {
311 s_sharedInfoView.init(title, text);
312 if (hint) {
313 s_sharedInfoView.setHint(hint);
314 }
316 return &s_sharedInfoView;
317}
318
319} // 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 int TEXT_START_Y
Definition InfoView.cpp:27
static constexpr int TEXT_MARGIN
Definition InfoView.cpp:28
constexpr int FOOTER_HEIGHT
Footer bar height in pixels (used by drawFooterBar).
static constexpr int TITLE_Y
Display layout constants.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_D(tag, fmt,...)
Definition cdc_log.h:148
void render(bool partial) override
Renders the info view including title, body, scroll indicator, and footer.
Definition InfoView.cpp:206
static constexpr uint16_t MAX_TEXT_LEN
Definition InfoView.h:22
InputResult onKey(char key) override
Handles key input for scrolling and optional callbacks.
Definition InfoView.cpp:143
static constexpr uint8_t VISIBLE_LINES
Definition InfoView.h:23
InputResult onLongPress(char key) override
Long-press jumps to the first (KEY_UP) or last (KEY_DOWN) page.
Definition InfoView.cpp:181
static constexpr uint8_t LINE_HEIGHT
Definition InfoView.h:24
void init(const char *title, const char *text)
Definition InfoView.cpp:66
const char * getFooterHint() const override
Returns the footer hint text.
Definition InfoView.cpp:194
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:34
void push(IView *view, void *context=nullptr)
PsramUniquePtr< T > psramAlloc(std::size_t count) noexcept
Allocate count elements of T in PSRAM (8-bit capable region).
Definition Raii.h:51
IDisplay * getDisplayInstance()
Returns lazily created singleton display instance.
constexpr int SCROLL_INDICATOR_WIDTH
Scroll indicator column width in pixels.
constexpr int FOOTER_HEIGHT
Footer bar height in pixels (used by drawFooterBar).
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 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:208
InfoView * showInfo(const char *title, const char *text, const char *hint=nullptr)
Shows a shared info view instance and pushes it onto the view stack.
Definition InfoView.cpp:310
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:10
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.
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
static InfoView s_sharedInfoView
Convenience factory/helper function.
Definition InfoView.cpp:301
static constexpr int TEXT_MARGIN