CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
MarkdownView.cpp
Go to the documentation of this file.
1
6
8
9#include "cdc_views/Fonts.h"
10#include "cdc_views/KeyCodes.h"
15#include "cdc_ui/I18n.h"
16#include "cdc_ui/ViewStack.h"
17#include "cdc_hal/IDisplay.h"
18#include "cdc_log.h"
19
20#include <goodisplay/gdey029T94.h>
21
22#include <cstring>
23#include <cstdio>
24
25static const char* TAG = "MarkdownView";
26
27static constexpr int TITLE_Y = 5;
28static constexpr int TEXT_START_Y = 28;
29static constexpr int TEXT_MARGIN = 8;
30static constexpr int INDENT_PX = 10; // per nesting level
31static constexpr int BLANK_HEIGHT = 6;
32static constexpr int RULE_HEIGHT = 10;
35
36namespace cdc::ui {
37
38namespace {
39
41int charWidth(uint8_t fontId) {
42 const GFXfont* f = getGfxFont(fontId);
43 if (!f || !f->glyph) return 6;
44 int adv = f->glyph[0].xAdvance;
45 return adv > 0 ? adv : 6;
46}
47
49int fontHeight(uint8_t fontId) {
50 const GFXfont* f = getGfxFont(fontId);
51 if (!f) return 12;
52 return f->yAdvance;
53}
54
56int ascentOffset(Gdey029T94* gfx, uint8_t fontId) {
57 const GFXfont* f = getGfxFont(fontId);
58 if (!f) return 0; // built-in font uses a top-left origin
59 int16_t x1 = 0, y1 = 0;
60 uint16_t w = 0, h = 0;
61 render::measureText(gfx, "Mg", f, 0, 0, &x1, &y1, &w, &h);
62 return -y1;
63}
64
66void stripInline(const char* text, uint16_t len, char* out, size_t outCap) {
67 size_t o = 0;
68 for (uint16_t i = 0; i < len && o + 1 < outCap; ++i) {
69 const char c = text[i];
70 if (c == '*' || c == '_' || c == '`' || c == '\x10' || c == '\x11' || c == '\x12') continue;
71 out[o++] = c;
72 }
73 out[o] = '\0';
74}
75
76} // namespace
77
78void MarkdownView::addRow(const char* text, uint16_t len, uint8_t font, uint8_t indent,
79 MdLineKind kind, bool inverted, bool first) {
80 if (!rows_ || rowCount_ >= MAX_ROWS) {
81 truncated_ = true;
82 return;
83 }
84 Row& r = rows_.get()[rowCount_++];
85 r.text = text;
86 r.len = len;
87 r.font = font;
88 r.indent = indent;
89 r.kind = kind;
90 r.inverted = inverted;
91 r.first = first;
92}
93
95 if (line.kind == MdLineKind::Blank || line.kind == MdLineKind::Rule) {
96 addRow(line.text, 0, mdfont::Builtin, line.indent, line.kind, false, true);
97 return;
98 }
99
100 const int markerCols = (line.kind == MdLineKind::Bullet ||
101 line.kind == MdLineKind::Ordered ||
102 line.kind == MdLineKind::Quote)
103 ? 2
104 : 0;
105 const int cw = charWidth(line.font);
106 int avail = textAreaWidth_ - line.indent * INDENT_PX - markerCols * cw;
107 int maxCols = avail / (cw > 0 ? cw : 6);
108 if (maxCols < 4) maxCols = 4;
109
110 // Word-wrap the span into <= maxCols chunks, breaking on spaces.
111 uint16_t start = 0;
112 bool first = true;
113 while (start < line.len) {
114 uint16_t remaining = static_cast<uint16_t>(line.len - start);
115 uint16_t take = remaining <= maxCols ? remaining : static_cast<uint16_t>(maxCols);
116 if (take < remaining) {
117 // back up to the last space within [start, start+take]
118 uint16_t brk = take;
119 while (brk > 0 && line.text[start + brk] != ' ') --brk;
120 if (brk > 0) take = brk;
121 }
122 addRow(line.text + start, take, line.font, line.indent, line.kind,
123 line.inverted, first);
124 start = static_cast<uint16_t>(start + take);
125 while (start < line.len && line.text[start] == ' ') ++start; // skip the break space
126 first = false;
127 }
128 if (line.len == 0) {
129 addRow(line.text, 0, line.font, line.indent, line.kind, line.inverted, true);
130 }
131}
132
133namespace {
134struct RowSink : StyledLineSink {
135 MarkdownView* view;
136 explicit RowSink(MarkdownView* v) : view(v) {}
137 void emit(const StyledLine& l) override { view->appendParsed(l); }
138};
139} // namespace
140
141void MarkdownView::init(const char* title, const char* src, size_t len) {
142 if (title) {
143 strncpy(titleBuf_, title, MAX_TITLE_LEN - 1);
144 titleBuf_[MAX_TITLE_LEN - 1] = '\0';
145 } else {
146 titleBuf_[0] = '\0';
147 }
148
149 if (!srcBuf_) srcBuf_ = cdc::core::psramAlloc<char>(MAX_SOURCE);
150 if (!rows_) rows_ = cdc::core::psramAlloc<Row>(MAX_ROWS);
151 if (!interact_) interact_ = cdc::core::psramAlloc<InteractRef>(MAX_INTERACT);
152
153 rowCount_ = 0;
154 scrollRow_ = 0;
155 truncated_ = false;
156 plain_ = false;
157 highlightRow_ = -1;
158 interactCount_ = 0;
159 interactSel_ = -1;
160 inlineLinkCount_ = 0;
161 linkPoolLen_ = 0;
162 checkSave_ = nullptr;
163 checkSaveUd_ = nullptr;
164
166 const uint16_t width = display ? display->getWidth() : 296;
167 textAreaWidth_ = width - TEXT_MARGIN * 2 - SCROLL_INDICATOR_WIDTH;
168
169 if (srcBuf_ && rows_) {
170 size_t n = len < (MAX_SOURCE - 1) ? len : (MAX_SOURCE - 1);
171 std::memcpy(srcBuf_.get(), src ? src : "", n);
172 srcBuf_.get()[n] = '\0';
173 rewriteInlineLinks(); // "[text](url)" -> "\x10text\x11", url pool
174 size_t pn = std::strlen(srcBuf_.get());
175
176 RowSink sink(this);
177 auto r = parseMarkdown(srcBuf_.get(), pn, sink, MAX_SOURCE);
178 if (r.truncated || len >= MAX_SOURCE) truncated_ = true;
179 }
180
182 dirty_ = true;
183 LOG_D(TAG, "init: title='%s', rows=%u, trunc=%d", titleBuf_, rowCount_, truncated_);
184}
185
186void MarkdownView::initPlain(const char* title, const char* src, size_t len) {
187 if (title) {
188 strncpy(titleBuf_, title, MAX_TITLE_LEN - 1);
189 titleBuf_[MAX_TITLE_LEN - 1] = '\0';
190 } else {
191 titleBuf_[0] = '\0';
192 }
193
194 if (!srcBuf_) srcBuf_ = cdc::core::psramAlloc<char>(MAX_SOURCE);
195 if (!rows_) rows_ = cdc::core::psramAlloc<Row>(MAX_ROWS);
196 if (!interact_) interact_ = cdc::core::psramAlloc<InteractRef>(MAX_INTERACT);
197
198 rowCount_ = 0;
199 scrollRow_ = 0;
200 truncated_ = false;
201 plain_ = true;
202 highlightRow_ = -1;
203 interactCount_ = 0;
204 interactSel_ = -1;
205 inlineLinkCount_ = 0;
206 linkPoolLen_ = 0;
207 checkSave_ = nullptr;
208 checkSaveUd_ = nullptr;
209
211 const uint16_t width = display ? display->getWidth() : 296;
212 textAreaWidth_ = width - TEXT_MARGIN * 2 - SCROLL_INDICATOR_WIDTH;
213
214 if (srcBuf_ && rows_) {
215 size_t n = len < (MAX_SOURCE - 1) ? len : (MAX_SOURCE - 1);
216 if (len >= MAX_SOURCE) truncated_ = true;
217 std::memcpy(srcBuf_.get(), src ? src : "", n);
218 srcBuf_.get()[n] = '\0';
219
220 const int cw = charWidth(mdfont::Builtin);
221 int maxCols = textAreaWidth_ / (cw > 0 ? cw : 6);
222 if (maxCols < 4) maxCols = 4;
223
224 const char* base = srcBuf_.get();
225 uint32_t off = 0;
226 while (off < n) {
227 uint32_t nl = off;
228 while (nl < n && base[nl] != '\n') ++nl;
229 uint16_t lineLen = static_cast<uint16_t>(nl - off);
230 if (lineLen == 0) {
231 addRow(base + off, 0, mdfont::Builtin, 0, MdLineKind::Paragraph, false, true);
232 }
233 uint16_t start = 0;
234 bool first = true;
235 while (start < lineLen) {
236 uint16_t remaining = static_cast<uint16_t>(lineLen - start);
237 uint16_t take = remaining <= maxCols ? remaining : static_cast<uint16_t>(maxCols);
238 if (take < remaining) {
239 uint16_t brk = take;
240 while (brk > 0 && base[off + start + brk] != ' ') --brk;
241 if (brk > 0) take = brk;
242 }
243 addRow(base + off + start, take, mdfont::Builtin, 0, MdLineKind::Paragraph, false,
244 first);
245 start = static_cast<uint16_t>(start + take);
246 while (start < lineLen && base[off + start] == ' ') ++start;
247 first = false;
248 }
249 off = (nl < n) ? nl + 1 : nl;
250 }
251 }
252
253 dirty_ = true;
254 LOG_D(TAG, "initPlain: title='%s', rows=%u, trunc=%d", titleBuf_, rowCount_, truncated_);
255}
256
257uint16_t MarkdownView::visibleRowCount() const {
258 return rowCount_;
259}
260
262 srcBuf_.reset();
263 rows_.reset();
264 interact_.reset();
265 linkPool_.reset();
266 linkUrlOff_.reset();
267 rowCount_ = 0;
268 interactCount_ = 0;
269 interactSel_ = -1;
270 inlineLinkCount_ = 0;
271 linkPoolLen_ = 0;
272}
273
274const char* MarkdownView::getFooterHint() const {
275 return ui::tr("core.hint_scroll_back");
276}
277
279 switch (key) {
280 case KEY_UP:
281 if (scrollRow_ > 0) { scrollRow_--; dirty_ = true; }
283 case KEY_DOWN:
284 if (scrollRow_ + 1 < rowCount_) { scrollRow_++; dirty_ = true; }
286 case '4':
287 if (interactCount_ == 0) return InputResult::IGNORED;
290 case '6':
291 if (interactCount_ == 0) return InputResult::IGNORED;
294 case KEY_YES:
295 if (!selectedRef()) return InputResult::IGNORED;
298 case KEY_NO:
300 default:
302 }
303}
304
306 if (rowCount_ == 0) return InputResult::IGNORED;
307 switch (key) {
308 case KEY_UP: scrollRow_ = 0; dirty_ = true; return InputResult::CONSUMED;
309 case KEY_DOWN: scrollRow_ = static_cast<uint16_t>(rowCount_ - 1); dirty_ = true; return InputResult::CONSUMED;
310 default: return InputResult::IGNORED;
311 }
312}
313
314void MarkdownView::selectMarker(uint16_t linkNum) {
315 highlightRow_ = -1;
316 if (linkNum != 0 && rows_) {
317 char needle[12];
318 std::snprintf(needle, sizeof(needle), "[%u]", static_cast<unsigned>(linkNum));
319 size_t nl = std::strlen(needle);
320 for (uint16_t i = 0; i < rowCount_ && highlightRow_ < 0; ++i) {
321 const Row& r = rows_.get()[i];
322 if (r.len < nl) continue;
323 for (uint16_t j = 0; j + nl <= r.len; ++j) {
324 if (std::memcmp(r.text + j, needle, nl) == 0) {
325 highlightRow_ = static_cast<int>(i);
326 break;
327 }
328 }
329 }
330 if (highlightRow_ >= 0) scrollRow_ = static_cast<uint16_t>(highlightRow_);
331 }
332 dirty_ = true;
333}
334
335void MarkdownView::mdRowInfo(uint16_t i, const char*& text, uint16_t& len, bool& first,
336 uint32_t& srcOff) const {
337 const Row& r = rows_.get()[i];
338 text = r.text;
339 len = r.len;
340 first = r.first;
341 srcOff = srcBuf_ ? static_cast<uint32_t>(r.text - srcBuf_.get()) : 0;
342}
343
344void MarkdownView::addInteract(uint16_t row, Interact kind, uint16_t id, uint32_t srcOff) {
345 if (!interact_ || interactCount_ >= MAX_INTERACT) return;
346 interact_.get()[interactCount_++] = InteractRef{row, kind, id, srcOff};
347}
348
350 if (interactSel_ < 0 || interactSel_ >= static_cast<int>(interactCount_)) return nullptr;
351 return &interact_.get()[interactSel_];
352}
353
354namespace {
355// A task-list checkbox at the start of a list item: "[ ]", "[x]" or "[X]".
356bool checkboxAt(const char* t, uint16_t len) {
357 return len >= 3 && t[0] == '[' && t[2] == ']' &&
358 (t[1] == ' ' || t[1] == 'x' || t[1] == 'X');
359}
360} // namespace
361
362void MarkdownView::rewriteInlineLinks() {
363 inlineLinkCount_ = 0;
364 linkPoolLen_ = 0;
365 if (!srcBuf_) return;
366 char* s = srcBuf_.get();
367 size_t rd = 0, wr = 0;
368 while (s[rd]) {
369 bool image = (s[rd] == '!' && s[rd + 1] == '[');
370 if (s[rd] == '[' || image) {
371 size_t tb = image ? rd + 2 : rd + 1; // text begin
372 size_t te = tb;
373 while (s[te] && s[te] != ']' && s[te] != '\n') ++te;
374 if (s[te] == ']' && s[te + 1] == '(') {
375 size_t ub = te + 2, ue = ub; // url
376 while (s[ue] && s[ue] != ')' && s[ue] != '\n') ++ue;
377 size_t urlLen = ue - ub;
378 if (s[ue] == ')' && urlLen > 0) {
379 if (!linkPool_) linkPool_ = cdc::core::psramAlloc<char>(kLinkPoolCap);
380 if (!linkUrlOff_) linkUrlOff_ = cdc::core::psramAlloc<uint16_t>(MAX_INTERACT);
381 bool room = linkPool_ && linkUrlOff_ && inlineLinkCount_ < MAX_INTERACT &&
382 linkPoolLen_ + urlLen + 1 <= kLinkPoolCap;
383 if (room) {
384 linkUrlOff_.get()[inlineLinkCount_++] = linkPoolLen_;
385 std::memcpy(linkPool_.get() + linkPoolLen_, s + ub, urlLen);
386 linkPool_.get()[linkPoolLen_ + urlLen] = '\0';
387 linkPoolLen_ = static_cast<uint16_t>(linkPoolLen_ + urlLen + 1);
388 s[wr++] = image ? '\x12' : '\x10';
389 for (size_t k = tb; k < te; ++k) s[wr++] = s[k];
390 s[wr++] = '\x11';
391 rd = ue + 1;
392 continue;
393 }
394 }
395 }
396 }
397 s[wr++] = s[rd++];
398 }
399 s[wr] = '\0';
400}
401
404 if (!rows_ || plain_) return;
405 uint16_t ordinal = 0;
406 for (uint16_t i = 0; i < rowCount_; ++i) {
407 const Row& r = rows_.get()[i];
408 const uint32_t base = srcBuf_ ? static_cast<uint32_t>(r.text - srcBuf_.get()) : 0;
409
410 // Task-list checkbox: only on a list item, so a "[x](url)" link is not one.
411 if (r.first && (r.kind == MdLineKind::Bullet || r.kind == MdLineKind::Ordered) &&
412 checkboxAt(r.text, r.len)) {
413 addInteract(i, Interact::Check, 0, base + 1);
414 }
415
416 // Inline links/images are delimited (\x10 link / \x12 image, both end \x11).
417 for (uint16_t j = 0; j < r.len; ++j) {
418 char c = r.text[j];
419 if (c == '\x10') addInteract(i, Interact::Link, ordinal++, base + j);
420 else if (c == '\x12') addInteract(i, Interact::Image, ordinal++, base + j);
421 }
422 }
423}
424
426 if (interactCount_ == 0) return;
427 if (interactSel_ < 0) {
428 interactSel_ = (dir > 0) ? 0 : interactCount_ - 1;
429 } else {
430 interactSel_ = ((interactSel_ + dir) % interactCount_ + interactCount_) % interactCount_;
431 }
432 const InteractRef& it = interact_.get()[interactSel_];
433 highlightRow_ = static_cast<int>(it.row);
434 scrollRow_ = it.row;
435 dirty_ = true;
436}
437
438bool MarkdownView::flipCheckChar(uint32_t srcOff) {
439 if (!srcBuf_ || srcOff == 0) return false;
440 char* p = srcBuf_.get() + srcOff;
441 bool nowChecked = (*p == ' ');
442 *p = nowChecked ? 'x' : ' ';
443 dirty_ = true;
444 return nowChecked;
445}
446
448 if (checkSave_ && srcBuf_) checkSave_(checkSaveUd_, srcBuf_.get(), std::strlen(srcBuf_.get()));
449}
450
452 const InteractRef* it = selectedRef();
453 if (!it) return;
454 if (it->kind == Interact::Check) {
457 } else if (it->kind == Interact::Link || it->kind == Interact::Image) {
458 if (!linkPool_ || !linkUrlOff_ || it->id >= inlineLinkCount_) return;
459 const char* url = linkPool_.get() + linkUrlOff_.get()[it->id];
460 if (it->kind == Interact::Link) {
461 if (auto open = urlOpener()) open(url);
462 } else {
463 if (auto open = imageOpener()) open(url);
464 }
465 }
466}
467
468void MarkdownView::drawStyledRow(void* gfxv, const char* text, uint16_t len, int x, int yTop,
469 uint16_t fg) {
470 auto* gfx = static_cast<Gdey029T94*>(gfxv);
471 gfx->setFont(nullptr);
472 bool bold = false, strike = false, link = false;
473 int cx = x;
474 for (uint16_t i = 0; i < len;) {
475 char c = text[i];
476 if (c == '\x10' || c == '\x12') { link = true; ++i; continue; } // link / image start
477 if (c == '\x11') { link = false; ++i; continue; } // link / image end
478 if (c == '`') { ++i; continue; } // inline code marker
479 if (c == '*' || c == '_') {
480 if (i + 1 < len && text[i + 1] == c) { bold = !bold; i += 2; } // ** / __ = bold
481 else ++i; // single * / _ = italic: not renderable in the bitmap font, shown plain
482 continue;
483 }
484 if (c == '~' && i + 1 < len && text[i + 1] == '~') { strike = !strike; i += 2; continue; }
485 gfx->setCursor(cx, yTop);
486 gfx->write(static_cast<uint8_t>(c));
487 if (bold) { gfx->setCursor(cx + 1, yTop); gfx->write(static_cast<uint8_t>(c)); } // faux-bold
488 if (link) gfx->drawLine(cx, yTop + 7, cx + 5, yTop + 7, fg);
489 if (strike) gfx->drawLine(cx, yTop + 3, cx + 5, yTop + 3, fg);
490 cx += 6;
491 ++i;
492 }
493}
494
495void MarkdownView::render(bool partial) {
497 if (!display) return;
498 auto* gfx = static_cast<Gdey029T94*>(display->getNativeHandle());
499 if (!gfx) return;
500
501 const uint16_t width = display->getWidth();
502 const uint16_t height = display->getHeight();
503
504 if (!partial) {
505 gfx->fillScreen(EPD_WHITE);
506 } else {
507 gfx->fillRect(0, 0, width, height - FOOTER_HEIGHT, EPD_WHITE);
508 }
509
510 gfx->setFont(nullptr);
511 gfx->setTextColor(EPD_BLACK);
512 gfx->setTextSize(1);
513
514 const char* title = (titleBuf_[0] != '\0') ? titleBuf_ : nullptr;
515 render::drawHeaderLeft(gfx, title, TEXT_MARGIN, TITLE_Y, width);
516
517 const int areaBottom = height - FOOTER_HEIGHT;
518 int y = TEXT_START_Y;
519 char buf[96];
520
521 uint16_t shown = 0;
522 for (uint16_t i = scrollRow_; i < rowCount_; ++i) {
523 const Row& r = rows_.get()[i];
524
525 if (r.kind == MdLineKind::Blank) {
526 if (y + BLANK_HEIGHT > areaBottom) break;
527 y += BLANK_HEIGHT;
528 ++shown;
529 continue;
530 }
531 if (r.kind == MdLineKind::Rule) {
532 if (y + RULE_HEIGHT > areaBottom) break;
533 gfx->drawLine(TEXT_MARGIN, y + RULE_HEIGHT / 2,
534 TEXT_MARGIN + textAreaWidth_, y + RULE_HEIGHT / 2, EPD_BLACK);
535 y += RULE_HEIGHT;
536 ++shown;
537 continue;
538 }
539
540 const int h = fontHeight(r.font);
541 if (y + h > areaBottom) break;
542
543 int x = TEXT_MARGIN + r.indent * INDENT_PX;
544
545 // Inverse background for code rows and the selected-marker row.
546 const bool inv = r.inverted || (static_cast<int>(i) == highlightRow_);
547 if (inv) {
548 gfx->fillRect(x, y, textAreaWidth_ - r.indent * INDENT_PX, h, EPD_BLACK);
549 gfx->setTextColor(EPD_WHITE);
550 }
551
552 // Block marker on the first visual row of a list item / quote.
553 if (r.first && (r.kind == MdLineKind::Bullet || r.kind == MdLineKind::Ordered)) {
554 gfx->setFont(nullptr);
555 gfx->setCursor(x, y);
556 gfx->print(static_cast<char>(0x07)); // CP437 bullet
557 x += 2 * 6;
558 } else if (r.kind == MdLineKind::Quote) {
559 gfx->setFont(nullptr);
560 gfx->setCursor(x, y);
561 gfx->print(static_cast<char>(0xB3)); // CP437 vertical bar
562 x += 2 * 6;
563 }
564
565 if (!plain_ && r.font == mdfont::Builtin) {
566 // Body text: inline styling (bold/strikethrough + underlined links).
567 drawStyledRow(gfx, r.text, r.len, x, y, inv ? EPD_WHITE : EPD_BLACK);
568 } else {
569 if (plain_) {
570 uint16_t cl =
571 r.len < sizeof(buf) - 1 ? r.len : static_cast<uint16_t>(sizeof(buf) - 1);
572 std::memcpy(buf, r.text, cl);
573 buf[cl] = '\0';
574 } else {
575 stripInline(r.text, r.len, buf, sizeof(buf));
576 }
577 const GFXfont* font = getGfxFont(r.font);
578 gfx->setFont(font);
579 gfx->setCursor(x, y + ascentOffset(gfx, r.font));
580 render::drawText(gfx, buf, font);
581 }
582
583 if (inv) gfx->setTextColor(EPD_BLACK);
584
585 gfx->setFont(nullptr);
586 y += h;
587 ++shown;
588 }
589
590 if (rowCount_ > shown || scrollRow_ > 0) {
591 const int indicatorX = width - SCROLL_INDICATOR_WIDTH;
592 const int listHeight = areaBottom - TEXT_START_Y;
593 render::drawScrollIndicator(gfx, indicatorX, TEXT_START_Y, listHeight,
594 rowCount_, shown ? shown : 1, scrollRow_);
595 }
596
597 char posStr[24];
598 const char* prefix = nullptr;
599 if (truncated_) {
600 prefix = ui::tr("core.md_truncated");
601 } else if (rowCount_ > shown) {
602 snprintf(posStr, sizeof(posStr), "%u/%u ", scrollRow_ + 1, rowCount_);
603 prefix = posStr;
604 }
605 render::drawFooterBar(gfx, width, height, prefix, getFooterHint(), true);
606
607 dirty_ = false;
608}
609
611
612MarkdownView* showMarkdown(const char* title, const char* src, size_t len) {
613 s_sharedMarkdownView.init(title, src, len);
615 return &s_sharedMarkdownView;
616}
617
618MarkdownView* showPlainText(const char* title, const char* src, size_t len) {
619 s_sharedMarkdownView.initPlain(title, src, len);
621 return &s_sharedMarkdownView;
622}
623
624} // namespace cdc::ui
static const char * TAG
Optional HTML-rendering hook, decoupling HTML producers from consumers.
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
constexpr int FOOTER_HEIGHT
Footer bar height in pixels (used by drawFooterBar).
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
static constexpr int BLANK_HEIGHT
static constexpr int INDENT_PX
static constexpr int RULE_HEIGHT
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
Scrollable viewer that renders a Markdown source.
void initPlain(const char *title, const char *src, size_t len)
Loads a source as plain, unrendered text (no Markdown parsing).
const char * getFooterHint() const override
void cycleInteractive(int dir)
Move the interactive selection by dir (+1/-1), highlighting its row.
void selectMarker(uint16_t linkNum)
Highlight the row containing the inline marker "[linkNum]" and scroll it into view....
void fireCheckSave()
Run the persistence callback with the current source (if set).
void addInteract(uint16_t row, Interact kind, uint16_t id, uint32_t srcOff)
static constexpr uint32_t MAX_SOURCE
Source byte cap.
void init(const char *title, const char *src, size_t len)
Loads and lays out a Markdown source.
virtual void rebuildInteractive()
Rebuild the interactive-element list from the rendered rows. Base finds task-list checkboxes; subclas...
void onExit() override
void mdRowInfo(uint16_t i, const char *&text, uint16_t &len, bool &first, uint32_t &srcOff) const
void render(bool partial) override
InputResult onKey(char key) override
virtual void activateSelected()
Activate the selected element (Y). Base toggles checkboxes.
void appendParsed(const StyledLine &line)
Sink callback: wraps one parsed styled line into visual rows.
static constexpr uint16_t MAX_ROWS
Visual-row cap.
const InteractRef * selectedRef() const
InputResult onLongPress(char key) override
Interact
Interactive element kinds the view can select (4/6) and activate (Y).
bool flipCheckChar(uint32_t srcOff)
Toggle a checkbox state char in the source.
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:53
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 drawText(Adafruit_GFX *gfx, const char *text, const GFXfont *font)
Draws CP437-encoded text correctly for the given font: the built-in glcdfont (font == nullptr) is CP4...
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 measureText(Adafruit_GFX *gfx, const char *text, const GFXfont *font, int16_t x0, int16_t y0, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h)
Measures CP437 text exactly as drawText would render it with font, so width-based layout (centering,...
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:209
const GFXfont * getGfxFont(FontId id)
Resolves a FontId to its underlying GFX font pointer.
Definition Fonts.cpp:26
MarkdownView * showMarkdown(const char *title, const char *src, size_t len)
Parses and shows a Markdown source on the view stack.
static constexpr char KEY_DOWN
Move selection down (numeric '8').
Definition KeyCodes.h:35
MdLineKind
Block role of a styled line, used by the view to apply layout/markers.
Gdey029T94 * display
UrlOpenerFn urlOpener()
Current URL opener, or nullptr when none is registered.
static MarkdownView s_sharedMarkdownView
InputResult
Definition IView.h:11
static constexpr char KEY_NO
Cancel / Back / Backspace.
Definition KeyCodes.h:44
MarkdownParseResult parseMarkdown(const char *src, size_t len, StyledLineSink &sink, size_t maxBytes)
Parses Markdown source into styled logical lines via a sink.
static constexpr int TITLE_Y
Layout constants mirror the ones used by T9InputView.
MarkdownView * showPlainText(const char *title, const char *src, size_t len)
Shows a source as plain, unrendered text on the view stack.
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
ImageOpenerFn imageOpener()
Current image opener, or nullptr when none is registered.
static constexpr int TEXT_MARGIN
One selectable interactive element discovered in the rendered rows.
uint16_t id
Link: 1-based marker number; Check/Submit: ordinal.
uint16_t row
Visual row index (highlight/scroll target).
uint32_t srcOff
Check: byte offset of the state char in the source; else 0.
Sink the parser emits styled lines to (decouples model from storage).
One rendered logical line produced by the Markdown parser.
uint16_t len
Span length in bytes.
uint8_t font
One of mdfont.
uint8_t indent
Nesting depth (view multiplies by columns).
const char * text
Span into source (not null-terminated).
bool inverted
Inverse background (code).