28SemaphoreHandle_t listEditMutex()
30 static SemaphoreHandle_t m = xSemaphoreCreateRecursiveMutex();
38 static PluginUiState s;
44void PluginUiState::onListSelect(uint16_t index,
void* userData)
46 auto* state =
static_cast<ListState*
>(userData);
49 state = s.list_.get();
52 uint32_t item_id = (index < state->count && state->item_ids)
53 ? state->item_ids[index] : 0;
54 uint32_t action = state->select_action_id;
61 if (!text || *text ==
'\0') {
62 list_->view->setEmptyText(
nullptr);
63 list_->empty_buf.reset();
69 std::memcpy(buf.get(), cp.c_str(), cp.size() + 1);
70 list_->view->setEmptyText(buf.get());
71 list_->empty_buf = std::move(buf);
81 if (list_ && list_->view && top == list_->view.get()) slot = &list_->footer_buf;
82 else if (confirm_.view && top == confirm_.view.get()) slot = &confirm_.footer_buf;
83 else if (input_.t9_view && top == input_.t9_view.get()) slot = &input_.footer_buf;
84 else if (input_.pin_view && top == input_.pin_view.get()) slot = &input_.footer_buf;
85 else if (input_.slider_view && top == input_.slider_view.get()) slot = &input_.footer_buf;
86 else if (input_.date_view && top == input_.date_view.get()) slot = &input_.footer_buf;
87 else if (input_.time_view && top == input_.time_view.get()) slot = &input_.footer_buf;
88 else if (input_.color_view && top == input_.color_view.get()) slot = &input_.footer_buf;
89 else if (canvas_.view && top == canvas_.view.get()) slot = &canvas_.footer_buf;
92 if (!hint || *hint ==
'\0') {
93 top->setFooterHint(
nullptr);
100 std::memcpy(buf.get(), cp.c_str(), cp.size() + 1);
101 top->setFooterHint(buf.get());
102 *slot = std::move(buf);
113 lifecycle_hide_action_ = hide_action_id;
114 lifecycle_show_action_ = show_action_id;
115 top->setLifecycleHooks(hide_action_id ? &onViewHide :
nullptr,
116 show_action_id ? &onViewShow :
nullptr,
nullptr);
128 vs.removeModal(ctxmenu_.view.get());
129 vs.removeModal(confirm_.view.get());
132 list_graveyard_.clear();
133 ctxmenu_ = ContextMenuState{};
134 confirm_ = ConfirmState{};
135 input_ = InputState{};
136 canvas_ = CanvasState{};
137 exclusive_token_ =
nullptr;
138 inactivity_action_ = 0;
141void PluginUiState::onListMenu(uint16_t index,
void* userData)
143 auto* state =
static_cast<ListState*
>(userData);
146 state = s.list_.get();
148 if (!state || state->menu_action_id == 0)
return;
149 uint32_t item_id = (index < state->count && state->item_ids)
150 ? state->item_ids[index] : 0;
154void PluginUiState::onConfirmYes(
void*)
157 uint32_t action = s.confirm_.action_id;
158 s.confirm_.action_id = 0;
162void PluginUiState::onConfirmNo(
void*)
165 uint32_t action = s.confirm_.action_id;
166 s.confirm_.action_id = 0;
170void PluginUiState::onT9Save(
const char* text)
173 s.input_.last_text = text ? text :
"";
174 uint32_t action = s.input_.action_id;
175 auto len =
static_cast<uint32_t
>(s.input_.last_text.size());
176 s.input_.action_id = 0;
180bool PluginUiState::onPinVerify(
const char* pin)
183 s.input_.last_text = pin ? pin :
"";
184 uint32_t action = s.input_.action_id;
185 auto len =
static_cast<uint32_t
>(s.input_.last_text.size());
186 s.input_.action_id = 0;
191void PluginUiState::onSliderSave(uint16_t value)
194 s.input_.last_int =
static_cast<int32_t
>(value);
195 s.input_.has_int =
true;
196 uint32_t action = s.input_.action_id;
197 s.input_.action_id = 0;
201void PluginUiState::onDateSave(uint8_t day, uint8_t month, uint16_t year)
204 s.input_.last_date = (
static_cast<uint32_t
>(year) << 16) |
205 (
static_cast<uint32_t
>(month) << 8) |
206 static_cast<uint32_t
>(day);
207 uint32_t action = s.input_.action_id;
208 s.input_.action_id = 0;
212void PluginUiState::onTimeSave(uint8_t hour, uint8_t minute)
215 s.input_.last_time =
static_cast<uint16_t
>((hour << 8) | minute);
216 uint32_t action = s.input_.action_id;
217 s.input_.action_id = 0;
221void PluginUiState::onColorSave(uint8_t r, uint8_t g, uint8_t b)
224 uint32_t packed = (
static_cast<uint32_t
>(r) << 16)
225 | (
static_cast<uint32_t
>(g) << 8)
226 |
static_cast<uint32_t
>(b);
227 s.input_.last_int =
static_cast<int32_t
>(packed);
228 s.input_.has_int =
true;
229 uint32_t action = s.input_.action_id;
230 s.input_.action_id = 0;
234void PluginUiState::onInputCancel()
240 uint32_t action = s.input_.action_id;
241 s.input_.action_id = 0;
245void PluginUiState::onPinCancel()
251 uint32_t action = s.input_.action_id;
252 s.input_.action_id = 0;
256void PluginUiState::onInactivity()
258 uint32_t action =
instance().inactivity_action_;
262void PluginUiState::onViewHide(
void* )
264 uint32_t action =
instance().lifecycle_hide_action_;
268void PluginUiState::onViewShow(
void* )
270 uint32_t action =
instance().lifecycle_show_action_;
274void PluginUiState::onCanvasKey(
char key, uint32_t focused_widget)
276 uint32_t action =
instance().canvas_.key_action_id;
279 action, focused_widget,
static_cast<uint32_t
>(
static_cast<unsigned char>(key)));
283void PluginUiState::onCanvasLongPress(
char key)
285 uint32_t action =
instance().canvas_.long_press_action_id;
288 action, 0,
static_cast<uint32_t
>(
static_cast<unsigned char>(key)));
294 uint32_t action =
instance().canvas_.widget_action_id;
297 action, widget_id,
static_cast<uint32_t
>(event));
315 &ctxCb0, &ctxCb1, &ctxCb2, &ctxCb3,
316 &ctxCb4, &ctxCb5, &ctxCb6, &ctxCb7,
323 if (idx >= ctxmenu_.count)
return;
324 uint32_t item_id = ctxmenu_.item_ids ? ctxmenu_.item_ids[idx] : 0;
325 uint32_t action = ctxmenu_.select_action_id;
330 uint32_t select_action_id)
334 if (count > cap) count = cap;
336 ContextMenuState next;
338 size_t pool_bytes = 0;
339 for (uint16_t i = 0; i < count; ++i) {
341 pool_bytes += cpLabels[i].size() + 1;
346 if (!next.items || !next.string_pool || !next.item_ids) {
350 char* dst = next.string_pool.get();
351 for (uint16_t i = 0; i < count; ++i) {
352 size_t n = cpLabels[i].size();
353 std::memcpy(dst, cpLabels[i].c_str(), n);
355 next.items[i].label = dst;
356 next.items[i].callback = kCtxCallbacks[i];
357 next.item_ids[i] = items[i].
item_id;
361 next.select_action_id = select_action_id;
362 if (title && *title) {
366 std::memcpy(next.title_buf.get(), cpTitle.c_str(), cpTitle.size() + 1);
368 next.view = std::make_unique<cdc::ui::ContextMenuView>();
369 next.view->init(next.title_buf ? next.title_buf.get() :
"",
370 next.items.get(),
static_cast<uint8_t
>(count));
373 ctxmenu_ = std::move(next);
378 uint32_t select_action_id, uint32_t menu_action_id,
383 auto next = std::make_unique<ListState>();
387 if (!next->items || !next->item_ids) {
390 next->labels.reserve(count);
392 next->capacity = count;
394 for (uint16_t i = 0; i < count; ++i) {
395 std::string cp =
toDisplay(items[i].label);
398 std::memcpy(buf.get(), cp.c_str(), cp.size() + 1);
399 next->items[i].label = buf.get();
400 next->items[i].icon = items[i].
icon;
402 next->items[i].userData = next.get();
403 next->item_ids[i] = items[i].
item_id;
404 next->labels.push_back(std::move(buf));
407 next->select_action_id = select_action_id;
408 next->menu_action_id = menu_action_id;
412 std::memcpy(next->title_buf.get(), cpTitle.c_str(), cpTitle.size() + 1);
413 next->view = std::make_unique<cdc::ui::ListView>();
414 next->view->setEditMutex(listEditMutex());
415 next->view->init(next->title_buf.get(), next->items.get(), count);
416 next->view->setOnSelect(&PluginUiState::onListSelect);
417 if (menu_action_id != 0) next->view->setOnMenu(&PluginUiState::onListMenu);
420 const bool can_replace = (list_ && list_->view && stack.current() == list_->view.get());
421 if (can_replace && replace_top) {
422 stack.replace(next->view.get());
424 stack.push(next->view.get());
427 list_graveyard_.push_back(std::move(list_));
429 list_ = std::move(next);
436 if (!list_ || !list_->view || !list_->items || !list_->item_ids) {
451 std::memcpy(buf.get(), cp.c_str(), cp.size() + 1);
457 list_->items[index].label = buf.get();
458 list_->items[index].icon = item->
icon;
460 list_->item_ids[index] = item->
item_id;
461 list_->labels[index] = std::move(buf);
463 list_->view->updateItem(index);
468bool PluginUiState::growList(uint16_t need)
470 if (!list_)
return false;
471 if (list_->capacity >= need)
return true;
473 uint16_t newCap =
static_cast<uint16_t
>(list_->capacity + list_->capacity / 2 + 8);
474 if (newCap < need) newCap = need;
479 if (!ni || !nid)
return false;
480 for (uint16_t i = 0; i < list_->count; ++i) {
481 ni[i] = list_->items[i];
482 nid[i] = list_->item_ids[i];
484 list_->items = std::move(ni);
485 list_->item_ids = std::move(nid);
486 list_->capacity = newCap;
489 list_->view->preservePosition();
490 list_->view->init(list_->title_buf ? list_->title_buf.get() :
"",
491 list_->items.get(), list_->count);
498 if (!list_ || !list_->view) {
504 const uint16_t oldCount = list_->count;
506 if (index > oldCount) index = oldCount;
511 std::memcpy(buf.get(), cp.c_str(), cp.size() + 1);
518 for (uint16_t r = oldCount; r > index; --r) {
519 list_->items[r] = list_->items[r - 1];
520 list_->item_ids[r] = list_->item_ids[r - 1];
522 list_->labels.insert(list_->labels.begin() + index, std::move(buf));
523 list_->items[index].label = list_->labels[index].get();
524 list_->items[index].icon = item->
icon;
526 list_->items[index].userData = list_.get();
527 list_->item_ids[index] = item->
item_id;
528 list_->count =
static_cast<uint16_t
>(oldCount + 1);
529 list_->view->insertItem(index);
536 if (!list_ || !list_->view || !list_->items || !list_->item_ids) {
542 const uint16_t oldCount = list_->count;
548 list_->labels.erase(list_->labels.begin() + index);
550 for (uint16_t r = index; r + 1 < oldCount; ++r) {
551 list_->items[r] = list_->items[r + 1];
552 list_->item_ids[r] = list_->item_ids[r + 1];
554 list_->count =
static_cast<uint16_t
>(oldCount - 1);
555 list_->view->removeItem(index);
563 confirm_ = ConfirmState{};
564 confirm_.view = std::make_unique<cdc::ui::ConfirmView>();
565 confirm_.action_id = action_id;
567 confirm_.view->init(cpText.c_str(), toConfirmIcon(icon));
568 confirm_.view->setOnConfirm(&PluginUiState::onConfirmYes,
nullptr);
569 confirm_.view->setOnCancel (&PluginUiState::onConfirmNo,
nullptr);
575 uint16_t max_len, uint32_t action_id)
578 input_ = InputState{};
579 input_.action_id = action_id;
583 std::memcpy(input_.title_buf.get(), cpTitle.c_str(), cpTitle.size() + 1);
584 std::string cpInitial =
toDisplay(initial);
585 input_.t9_view = std::make_unique<cdc::ui::T9InputView>();
586 input_.t9_view->init(input_.title_buf.get(), initial ? cpInitial.c_str() :
nullptr, max_len);
587 input_.t9_view->setOnSave(&PluginUiState::onT9Save);
588 input_.t9_view->setOnCancel(&PluginUiState::onInputCancel);
597 input_ = InputState{};
598 input_.action_id = action_id;
600 input_.pin_view = std::make_unique<cdc::ui::PinEntryView>();
601 input_.pin_view->init(cpTitle.c_str(), max_len, max_attempts);
602 input_.pin_view->setOnVerify(&PluginUiState::onPinVerify);
603 input_.pin_view->setOnCancel(&PluginUiState::onPinCancel);
609 int32_t step,
const char* unit, uint32_t action_id)
612 input_ = InputState{};
613 input_.action_id = action_id;
617 std::memcpy(input_.title_buf.get(), cpTitle.c_str(), cpTitle.size() + 1);
621 std::memcpy(input_.unit_buf.get(), cpUnit.c_str(), cpUnit.size() + 1);
622 input_.slider_view = std::make_unique<cdc::ui::SliderView>();
623 input_.slider_view->init(input_.title_buf.get(), min, max, init, step, input_.unit_buf.get());
624 input_.slider_view->setOnSave(&PluginUiState::onSliderSave);
625 input_.slider_view->setOnCancel(&PluginUiState::onInputCancel);
634 input_ = InputState{};
635 input_.action_id = action_id;
637 input_.date_view = std::make_unique<cdc::ui::DateInputView>();
638 input_.date_view->init(cpTitle.c_str(), d, m, y);
639 input_.date_view->setOnConfirm(&PluginUiState::onDateSave);
640 input_.date_view->setOnCancel(&PluginUiState::onInputCancel);
648 input_ = InputState{};
649 input_.action_id = action_id;
651 input_.time_view = std::make_unique<cdc::ui::TimeInputView>();
652 input_.time_view->init(cpTitle.c_str(), h, m);
653 input_.time_view->setOnConfirm(&PluginUiState::onTimeSave);
654 input_.time_view->setOnCancel(&PluginUiState::onInputCancel);
661 input_ = InputState{};
662 input_.action_id = action_id;
663 input_.color_view = std::make_unique<cdc::ui::ColorPickerView>();
664 input_.color_view->init(r, g, b);
665 input_.color_view->setOnSave(&PluginUiState::onColorSave);
666 input_.color_view->setOnCancel(&PluginUiState::onInputCancel);
672 uint32_t widget_action_id)
674 canvas_ = CanvasState{};
676 if (title && *title) {
680 std::memcpy(canvas_.title_buf.get(), cp.c_str(), cp.size() + 1);
683 canvas_.key_action_id = key_action_id;
684 canvas_.widget_action_id = widget_action_id;
685 canvas_.view = std::make_unique<cdc::ui::CanvasView>();
686 canvas_.view->init(canvas_.title_buf.get());
687 canvas_.view->setKeyCallback(&PluginUiState::onCanvasKey);
688 canvas_.view->setWidgetCallback(&PluginUiState::onCanvasWidget);
697 canvas_.long_press_action_id = action_id;
698 canvas_.view->setLongPressCallback(action_id ? &PluginUiState::onCanvasLongPress :
nullptr);
704 return canvas_.view.get();
711 exclusive_token_ = plugin;
719 exclusive_token_ =
nullptr;
725 inactivity_action_ = action_id;
727 action_id ? &PluginUiState::onInactivity :
nullptr,
728 action_id ? timeout_ms : 0);
735 int n =
copyUtf8(input_.last_text.c_str(), out, out_size);
736 input_.last_text.clear();
744 *out = input_.last_int;
746 input_.has_int =
false;
Discovers, loads, runs and unloads WASM plugins on the badge.
void * plg_get_active_plugin(void)
Singleton that owns plugin-pushed UI views (lists, confirms, inputs).
Scoped guard for a FreeRTOS recursive mutex.
void dispatchAction(uint32_t action_id, uint32_t idx, uint32_t user_data)
static PluginManager & instance() noexcept
int pushPin(const char *title, uint8_t max_len, uint8_t max_attempts, uint32_t action_id)
int setCanvasLongPressAction(uint32_t action_id)
int pushCanvas(const char *title, uint32_t key_action_id, uint32_t widget_action_id)
int setViewLifecycle(uint32_t hide_action_id, uint32_t show_action_id)
int pushContextMenu(const char *title, const ui_item_t *items, uint16_t count, uint32_t select_action_id)
int removeListItem(uint16_t index)
Remove the row at index from the current plugin list (rebuild + partial refresh).
int insertListItem(uint16_t index, const ui_item_t *item)
Insert a row at index into the current plugin list (rebuild + partial refresh).
int pushList(const char *title, const ui_item_t *items, uint16_t count, uint32_t select_action_id, uint32_t menu_action_id, bool replace_top=false)
int pushTime(const char *title, uint8_t h, uint8_t m, uint32_t action_id)
int setViewFooter(const char *hint)
int pushT9(const char *title, const char *initial, uint16_t max_len, uint32_t action_id)
void dispatchContextSelect(uint8_t idx)
int pushDate(const char *title, uint8_t d, uint8_t m, uint16_t y, uint32_t action_id)
int pushConfirm(const char *text, uint8_t icon, uint32_t action_id)
int updateListItem(uint16_t index, const ui_item_t *item)
Redraw a single row of the current plugin list in place (partial refresh).
int setViewEmpty(const char *text)
cdc::ui::CanvasView * canvasView()
void resetForPluginStop()
int consumeInputText(char *out, size_t out_size)
int pushSlider(const char *title, int32_t min, int32_t max, int32_t init, int32_t step, const char *unit, uint32_t action_id)
int setInactivity(uint32_t timeout_ms, uint32_t action_id)
int consumeInputInt(int32_t *out)
static PluginUiState & instance() noexcept
int pushColorPicker(uint8_t r, uint8_t g, uint8_t b, uint32_t action_id)
Generic canvas view exposed to WASM plugins for custom UIs.
static constexpr uint16_t MAX_ITEMS
void setInactivityTimeout(InactivityCallback callback, uint32_t timeoutMs)
static ViewStack & instance()
Returns singleton view-stack instance.
void showModal(IView *modal)
bool releaseExclusive(const void *owner)
Releases exclusive ownership.
bool acquireExclusive(const void *owner)
Acquires exclusive ownership of the view stack.
void push(IView *view, void *context=nullptr)
#define HOST_ERR_NO_CAPABILITY
#define HOST_ERR_INVALID_ARG
#define HOST_ERR_NO_MEMORY
#define HOST_ERR_NOT_FOUND
void * plg_get_active_plugin(void)
Internal UTF-8 <-> CP437 helpers for the plugin host API boundary.
PsramUniquePtr< T > psramAlloc(std::size_t count) noexcept
::cdc::core::PsramUniquePtr< T > PsramUniquePtr
std::string toDisplay(const char *utf8)
Decode a UTF-8 (with optional HTML entities) string into CP437 bytes.
int copyUtf8(const char *cp437, char *out, size_t out_size)
Encode a CP437 string into a caller buffer as UTF-8.