CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
ToastView.cpp
Go to the documentation of this file.
1
6
10#include "cdc_ui/ViewStack.h"
11#include "cdc_hal/IDisplay.h"
12#include <goodisplay/gdey029T94.h>
13#include <cstring>
14#include "esp_timer.h"
15
16namespace cdc::ui {
17
26void ToastView::init(const char* message, Icon icon, uint16_t durationMs, bool dismissible) {
27 if (message) {
28 strncpy(message_, message, MAX_MSG_LEN - 1);
29 message_[MAX_MSG_LEN - 1] = '\0';
30 } else {
31 message_[0] = '\0';
32 }
33
34 icon_ = icon;
35 durationMs_ = durationMs;
36 dismissible_ = dismissible;
37 startMs_ = 0;
38 started_ = false;
39 expired_ = false;
40 dirty_ = true;
41}
42
48void ToastView::onTick(uint32_t nowMs) {
49 // started_ (not startMs_ == 0) marks "first render happened": a toast
50 // rendered at uptime 0 would otherwise never arm its auto-dismiss.
51 if (!started_ || expired_) return;
52 if (durationMs_ == 0) return;
53 if (nowMs < startMs_) return;
54 static constexpr uint32_t MIN_DISPLAY_MS = 1000;
55 uint32_t effective = durationMs_ < MIN_DISPLAY_MS ? MIN_DISPLAY_MS : durationMs_;
56 if (nowMs - startMs_ >= effective) {
57 expired_ = true;
59 }
60}
61
68 // Any Y or N key dismisses the toast (if dismissible)
69 if (dismissible_ && (key == KEY_YES || key == KEY_NO)) {
70 expired_ = true;
73 }
75}
76
82void ToastView::render(bool partial) {
83 (void)partial;
84
85 if (!started_) {
86 started_ = true;
87 startMs_ = esp_timer_get_time() / 1000;
88 }
89
91 if (!display) return;
92
93 auto* gfx = static_cast<Gdey029T94*>(display->getNativeHandle());
94 if (!gfx) return;
95
96 const uint16_t width = display->getWidth();
97 const uint16_t height = display->getHeight();
98
99 // Calculate centered position
100 int boxX = (width - BOX_WIDTH) / 2;
101 int boxY = (height - BOX_HEIGHT) / 2;
102
103 // Draw white box with double black border
104 render::drawDialogFrame(gfx, boxX, boxY, BOX_WIDTH, BOX_HEIGHT);
105
106 gfx->setTextColor(EPD_BLACK);
107 gfx->setTextSize(1);
108
109 // Text position (adjusted if icon present). y is the TOP of the glyph for
110 // size-1 glcdfont (8px tall), so subtract half the height to vertically center.
111 int textX = boxX + 15;
112 int textY = boxY + (BOX_HEIGHT / 2) - 4;
113
114 // Draw icon if present
115 if (icon_ != Icon::NONE) {
116 int iconX = boxX + 20;
117 int iconY = boxY + (BOX_HEIGHT / 2);
118
119 switch (icon_) {
120 case Icon::SUCCESS:
121 // Checkmark
122 gfx->drawLine(iconX - 5, iconY, iconX - 2, iconY + 4, EPD_BLACK);
123 gfx->drawLine(iconX - 2, iconY + 4, iconX + 6, iconY - 5, EPD_BLACK);
124 // Thicker
125 gfx->drawLine(iconX - 5, iconY + 1, iconX - 2, iconY + 5, EPD_BLACK);
126 gfx->drawLine(iconX - 2, iconY + 5, iconX + 6, iconY - 4, EPD_BLACK);
127 break;
128
129 case Icon::ERROR:
130 // X mark
131 gfx->drawLine(iconX - 5, iconY - 5, iconX + 5, iconY + 5, EPD_BLACK);
132 gfx->drawLine(iconX - 5, iconY + 5, iconX + 5, iconY - 5, EPD_BLACK);
133 // Thicker
134 gfx->drawLine(iconX - 4, iconY - 5, iconX + 6, iconY + 5, EPD_BLACK);
135 gfx->drawLine(iconX - 4, iconY + 5, iconX + 6, iconY - 5, EPD_BLACK);
136 break;
137
138 case Icon::INFO:
139 // Circle with i
140 gfx->drawCircle(iconX, iconY, 6, EPD_BLACK);
141 gfx->fillRect(iconX - 1, iconY - 3, 2, 2, EPD_BLACK); // Dot
142 gfx->fillRect(iconX - 1, iconY, 2, 5, EPD_BLACK); // Stem
143 break;
144 case Icon::TASK:
145 // Simple hourglass icon
146 gfx->drawLine(iconX - 5, iconY - 6, iconX + 5, iconY - 6, EPD_BLACK);
147 gfx->drawLine(iconX - 5, iconY + 6, iconX + 5, iconY + 6, EPD_BLACK);
148 gfx->drawLine(iconX - 5, iconY - 6, iconX + 5, iconY + 6, EPD_BLACK);
149 gfx->drawLine(iconX + 5, iconY - 6, iconX - 5, iconY + 6, EPD_BLACK);
150 gfx->fillTriangle(iconX - 3, iconY - 4, iconX + 3, iconY - 4, iconX, iconY - 1, EPD_BLACK);
151 gfx->fillTriangle(iconX - 3, iconY + 4, iconX + 3, iconY + 4, iconX, iconY + 1, EPD_BLACK);
152 break;
153 case Icon::ALERT:
154 // Warning triangle with exclamation
155 gfx->drawTriangle(iconX, iconY - 7, iconX - 6, iconY + 6, iconX + 6, iconY + 6, EPD_BLACK);
156 gfx->fillRect(iconX - 1, iconY - 2, 2, 5, EPD_BLACK);
157 gfx->fillRect(iconX - 1, iconY + 4, 2, 2, EPD_BLACK);
158 break;
159
160 default:
161 break;
162 }
163
164 textX = boxX + 40; // Shift text right when icon present
165 }
166
167 // Draw message text. Adafruit-GFX print() resets cursor_x to 0 on '\n',
168 // which would shoot the second line to the left edge of the screen. Render
169 // each line manually so it stays inside the modal frame, and shift the
170 // first line up to keep the whole block vertically centred.
171 constexpr int kLineHeight = 10; // size-1 font (~8px) + 2px spacing
172
173 size_t lineCount = 1;
174 for (const char* p = message_; *p; ++p) {
175 if (*p == '\n') ++lineCount;
176 }
177
178 int blockY = textY - static_cast<int>((lineCount - 1) * kLineHeight / 2);
179 const char* lineStart = message_;
180 int lineY = blockY;
181 for (const char* p = message_;; ++p) {
182 if (*p == '\n' || *p == '\0') {
183 char lineBuf[96];
184 size_t len = static_cast<size_t>(p - lineStart);
185 if (len >= sizeof(lineBuf)) len = sizeof(lineBuf) - 1;
186 memcpy(lineBuf, lineStart, len);
187 lineBuf[len] = '\0';
188 gfx->setCursor(textX, lineY);
189 // Clip to the box interior: text drawn past the frame is never
190 // cleared by drawDialogFrame, so on a partial refresh the overflow
191 // of the previous message stays on screen (ghosting).
192 render::printTruncated(gfx, lineBuf, (boxX + BOX_WIDTH) - textX - 8);
193 if (*p == '\0') break;
194 lineStart = p + 1;
195 lineY += kLineHeight;
196 }
197 }
198
199 dirty_ = false;
200}
201
205
207
216static void showToastInternal(const char* message, ToastView::Icon icon, uint16_t durationMs,
217 bool dismissible = true) {
218 s_sharedToast.init(message, icon, durationMs, dismissible);
220 ViewStack::instance().render(); // Immediate render for toast
221}
222
229void showToast(const char* message, uint16_t durationMs) {
230 showToastInternal(message, ToastView::Icon::NONE, durationMs);
231}
232
239void showToastSuccess(const char* message, uint16_t durationMs) {
240 showToastInternal(message, ToastView::Icon::SUCCESS, durationMs);
241}
242
249void showToastError(const char* message, uint16_t durationMs) {
250 showToastInternal(message, ToastView::Icon::ERROR, durationMs);
251}
252
259void showToastInfo(const char* message, uint16_t durationMs) {
260 showToastInternal(message, ToastView::Icon::INFO, durationMs);
261}
262
269void showToastTask(const char* message, uint16_t durationMs) {
270 showToastInternal(message, ToastView::Icon::TASK, durationMs);
271}
272
279void showToastAlert(const char* message, uint16_t durationMs) {
280 showToastInternal(message, ToastView::Icon::ALERT, durationMs);
281}
282
288void showToastAlertSticky(const char* message) {
289 showToastInternal(message, ToastView::Icon::ALERT, 0, false);
290}
291
292} // namespace cdc::ui
InputResult onKey(char key) override
Handles key input for optional toast dismissal.
Definition ToastView.cpp:67
void init(const char *message, Icon icon=Icon::NONE, uint16_t durationMs=1500, bool dismissible=true)
Initializes toast message content and timing behavior.
Definition ToastView.cpp:26
void onTick(uint32_t nowMs) override
Updates auto-dismiss timeout state.
Definition ToastView.cpp:48
void render(bool partial) override
Renders the toast overlay.
Definition ToastView.cpp:82
void render(bool synchronous=false)
Render current view (and modal if present) and flush to display.
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:53
void showModal(IView *modal)
IDisplay * getDisplayInstance()
Returns lazily created singleton display instance.
void drawDialogFrame(Gdey029T94 *gfx, int x, int y, int w, int h)
Draws a framed dialog box with double border.
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...
Centralized key-code constants for cdc_views.
Definition IModule.h:8
void showToast(const char *message, uint16_t durationMs=1500)
Shows a plain toast message.
static void showToastInternal(const char *message, ToastView::Icon icon, uint16_t durationMs, bool dismissible=true)
Shows the shared toast instance with custom icon and behavior.
void showToastTask(const char *message, uint16_t durationMs=0)
Shows a task/progress toast message.
Gdey029T94 * display
void showToastAlertSticky(const char *message)
Shows a non-dismissible alert toast.
InputResult
Definition IView.h:11
static constexpr char KEY_NO
Cancel / Back / Backspace.
Definition KeyCodes.h:44
void showToastAlert(const char *message, uint16_t durationMs=1500)
Shows an alert toast message.
void showToastSuccess(const char *message, uint16_t durationMs=1500)
Shows a success toast message.
static ToastView s_sharedToast
Convenience helper functions.
void showToastInfo(const char *message, uint16_t durationMs=1500)
Shows an informational toast message.
void showToastError(const char *message, uint16_t durationMs=1500)
Shows an error toast message.
static constexpr char KEY_YES
Confirm / OK / Save.
Definition KeyCodes.h:41