feat(sim): long-press context menu + pixel-perfect real font rendering

Two prototype polish fixes ahead of the website embed work:

1. Holding Enter (the sim's main way into "the rest of the options") did
   nothing -- the JS bridge's sim_enqueue_key() went straight to
   enqueueKey(), bypassing UITask::handleLongPress() entirely, so
   KEY_CONTEXT_MENU could never be reached. Add injectSimKeyLongPress()/
   sim_enqueue_key_longpress(), which does route through the real
   handleLongPress() (same code a real MomentaryButton(pin, 1000, ...)
   reaches), and wire up press-and-hold (buttons + Enter/Space key) in both
   web harnesses with the same 1000ms threshold real hardware uses.

2. SimDisplayDriverCanvas::print() drew text with the browser's own system
   font (ctx.fillText, '8px monospace') instead of the real bitmap font a
   MeshCore-Solo board renders with OLED_MISC_FIXED_FONT=1 (see
   solo/heltec_v3/platformio.ini). Vendor the real Adafruit_GFX (unmodified,
   from the same PlatformIO registry package a real board build pulls) into
   variants/sim/thirdparty/gfx/, and render print() through the real,
   shared src/helpers/ui/MiscFixedRenderer.h + MiscFixedFont.h -- byte-
   identical glyphs to real hardware, not a look-alike.

Verified in a real Chromium (Playwright): short Enter -> "CLOCK TOOLS",
held Enter -> "CLOCK FIELDS" (gotoDashboardConfig(), proving
KEY_CONTEXT_MENU is really reached); font renders as hard square pixels,
inverse/selected-row text still punches correctly through a filled bar.
Full regression re-run clean: all 3 native envs, wasm companion_radio, and
the two-instance + repeater mesh demo (relay routing, DM delivery).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-03 10:06:44 +02:00
co-authored by Claude Sonnet 5
parent 288a7d856b
commit 725e3b715e
14 changed files with 3765 additions and 35 deletions
@@ -2124,6 +2124,10 @@ void UITask::injectSimKey(char c) {
enqueueKey(checkDisplayOn(c));
}
void UITask::injectSimKeyLongPress(char c) {
enqueueKey(handleLongPress(c));
}
// Called directly from a host HTML page's JS (button onclick / keydown
// listener) -- e.g. `Module._sim_enqueue_key(keyCode)` -- to drive the real
// on-device menu. `c` is one of the KEY_* codes in src/helpers/ui/
@@ -2134,6 +2138,18 @@ void UITask::injectSimKey(char c) {
extern "C" EMSCRIPTEN_KEEPALIVE void sim_enqueue_key(char c) {
if (g_sim_ui_task_for_js) g_sim_ui_task_for_js->injectSimKey(c);
}
// Long-press counterpart -- the host page's own press-and-hold timer (see
// web/index.html/mesh.html) calls this instead of sim_enqueue_key() once a
// button/key has been held past the same ~1000ms threshold every real
// board's MomentaryButton uses. Real hardware never gets both a short-press
// AND a long-press event for the same physical press (MomentaryButton fires
// one or the other), so the host page's timer must do the same: fire this
// on hold-past-threshold and suppress the plain click that would otherwise
// follow on release.
extern "C" EMSCRIPTEN_KEEPALIVE void sim_enqueue_key_longpress(char c) {
if (g_sim_ui_task_for_js) g_sim_ui_task_for_js->injectSimKeyLongPress(c);
}
#endif
bool UITask::dequeueKey(char& c) {
+8
View File
@@ -219,6 +219,14 @@ public:
// sim's substitute for a real board's GPIO/joystick poll, not a new input
// path of its own.
void injectSimKey(char c);
// Same idea, but for a held-down press: routes through handleLongPress()
// instead of checkDisplayOn() directly -- the real long-press mechanism
// (KEY_ENTER -> KEY_CONTEXT_MENU, plus the first-8-seconds CLI-rescue
// gate) every real board's MomentaryButton(pin, 1000, ...) already
// reaches via BUTTON_EVENT_LONG_PRESS. Without this, a sim instance could
// never open the context menu at all -- injectSimKey() alone has no way
// to signal "this press was held".
void injectSimKeyLongPress(char c);
private:
#endif