From 3e5ccd28c704827f204bec19593690940f004dac Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Fri, 25 Sep 2026 00:53:23 +0200 Subject: [PATCH] feat(ui-lvgl): Nearby screen, node detail, scan popup; NearbyModel in the Core ui-core/NearbyModel.h builds the Nearby list (contacts + live [LOC] shares + heard adverts, or discover-scan results) with type filter and sort, moved verbatim out of ui-new's NearbyScreen, which now inherits it. ui-lvgl: Home is three tiles (Messages, Nearby, Settings). Nearby lists known nodes with filter chips and a Dist/Recent toggle; tapping a row opens node detail (distance/bearing, position, last heard, ID) with Message, Ping, favourite, Add and two-tap Delete. Scan is a popup over the list with its own model: nodes in range now, not the known set. Fonts gain the FontAwesome star for favourites. The LVGL sim page adds an optional repeater so Scan has something to find. Co-Authored-By: Claude Opus 5.5 --- docs/development/ui-core.md | 2 +- .../companion_radio/ui-core/NearbyModel.h | 321 +++++++++++ examples/companion_radio/ui-lvgl/Theme.h | 3 + examples/companion_radio/ui-lvgl/UITask.cpp | 499 +++++++++++++++++- examples/companion_radio/ui-lvgl/UITask.h | 41 +- .../companion_radio/ui-lvgl/fonts/generate.sh | 4 +- .../ui-lvgl/fonts/ui_font_12.c | 158 +++--- .../ui-lvgl/fonts/ui_font_14.c | 160 +++--- .../ui-lvgl/fonts/ui_font_16.c | 162 +++--- .../ui-lvgl/fonts/ui_font_20.c | 162 +++--- .../ui-lvgl/fonts/ui_font_40.c | 2 +- .../companion_radio/ui-new/NearbyScreen.h | 315 +---------- examples/companion_radio/ui-new/UITask.cpp | 1 + variants/sim/web/lvgl.html | 32 +- 14 files changed, 1252 insertions(+), 610 deletions(-) create mode 100644 examples/companion_radio/ui-core/NearbyModel.h diff --git a/docs/development/ui-core.md b/docs/development/ui-core.md index 93ffeb5e..ee50640a 100644 --- a/docs/development/ui-core.md +++ b/docs/development/ui-core.md @@ -160,7 +160,7 @@ checked in the sim plus on L1 hardware before the next one. 3. **Flip the interface** ✅. `UiCore` is `MyMesh::Listener`; `ui-new`'s `UITask` derives `UITaskBase` + `UiCoreHost` and is fed by events, drained at the start (mesh-originated) and end (engines) of its `loop()`. Mesh callbacks no longer touch the display or buzzer directly. 4. **Settings schema.** Convert `SettingsScreen` section by section. 5. **`ui-lvgl` skeleton** ✅ on L2 hardware (env `Wio_Tracker_L2_companion_solo_lvgl`, LVGL 9.2.2): status bar, home, conversation list, contact picker, conversation with keyboard, toasts, display sleep/wake. The Core gained the first actions (`sendDirectText`, `sendChannelText`) and runs DM resends itself. Then: European fonts + phone-style keyboard ✅, 320×240 sim target ✅. Next: screens by priority. -6. Contacts/Nearby, Admin, Bot logic extraction as the LVGL screens for them are built. +6. Contacts/Nearby, Admin, Bot logic extraction as the LVGL screens for them are built. Nearby ✅: `ui-core/NearbyModel.h` builds the list (contacts + live [LOC] shares + heard adverts, or scan results), filter and sort; `ui-new`'s `NearbyScreen` inherits it (entry array stays with the screen), `ui-lvgl` owns one for its Nearby list + node detail (Message, Ping, favourite, Add, Delete). Home became three tiles: Messages, Nearby, Settings. ## Decisions diff --git a/examples/companion_radio/ui-core/NearbyModel.h b/examples/companion_radio/ui-core/NearbyModel.h new file mode 100644 index 00000000..525df1d3 --- /dev/null +++ b/examples/companion_radio/ui-core/NearbyModel.h @@ -0,0 +1,321 @@ +#pragma once +// Nearby-nodes list model, shared by every frontend's Nearby screen: builds one +// list of entries from two sources and applies the type filter and sort. +// SRC_STORED -- contacts, with live [LOC] shares overlaid (fresher position, +// plus senders who aren't contacts) and, under the All filter, +// passively heard adverts that aren't listed yet +// SRC_SCAN -- NODE_DISCOVER_REQ results (RSSI / SNR), strongest first +// Selection, scrolling and actions stay in the frontend. Each screen owns its +// model (ui-new inherits it, so the entry array lives with the screen as +// before) and binds it to the Core for own position and live shares. +// +// Header-only; include after UiCore.h (needs the_mesh, rtc_clock, geo::). + +#include "../GeoUtils.h" + +class NearbyModel { +public: + enum Filter : uint8_t { F_ALL, F_FAV, F_COMP, F_RPT, F_ROOM, F_SNSR, F_COUNT }; + enum Sort : uint8_t { SORT_DIST, SORT_TIME }; + enum Source : uint8_t { SRC_STORED, SRC_SCAN }; + + struct Entry { + char name[32]; + uint8_t type; + uint8_t pub_key[PUB_KEY_SIZE]; + // has_key: the full 32-byte pubkey is present -- what Ping and the base64 + // key view need. has_prefix: at least the leading FAVOURITE_PREFIX_LEN + // bytes are, which is all an identity-keyed reference needs (a Locator + // person target). Every has_key row also has_prefix; the reverse doesn't + // hold -- a [LOC] share and a heard advert carry a prefix and nothing more. + bool has_key; + bool has_prefix; + // stored-source fields + int32_t lat_e6, lon_e6; + float dist_km; // -1 = unknown (no own fix or no node position) + uint32_t lastmod; + int contact_idx; // raw contact-table index, -1 = not a contact + // scan-source fields + int8_t rssi, snr_x4, remote_snr_x4; + bool is_known; + // live-track ([LOC] share) overlay: this row's position/age came from a + // shared-location message. live_verified: a DM (pubkey) share, not a + // channel (name, best-effort) one. + bool is_live; + bool live_verified; + // Favourite (ContactInfo::flags bit 0) -- only a contact carries it. + bool fav; + }; + + static const int MAX_NEARBY = 32; + + static const char* const* filterLabels() { + static const char* const L[F_COUNT] = { "All", "Fav", "Comp", "Rpt", "Room", "Snsr" }; + return L; + } + static const char* filterLabel(uint8_t f) { return f < F_COUNT ? filterLabels()[f] : ""; } + + static const char* typeName(uint8_t t) { + switch (t) { + case ADV_TYPE_CHAT: return "Companion"; + case ADV_TYPE_REPEATER: return "Repeater"; + case ADV_TYPE_ROOM: return "Room"; + case ADV_TYPE_SENSOR: return "Sensor"; + default: return "Unknown"; + } + } + + static const char* typeShort(uint8_t t) { + switch (t) { + case ADV_TYPE_REPEATER: return "Rpt"; + case ADV_TYPE_SENSOR: return "Snsr"; + case ADV_TYPE_ROOM: return "Room"; + case ADV_TYPE_CHAT: return "Comp"; + default: return "?"; + } + } + + void bindModel(UiCore* core, NodePrefs* prefs) { _core = core; _prefs = prefs; } + + int count() const { return _count; } + const Entry& at(int i) const { return _entries[i]; } + Source source() const { return _source; } + uint8_t filter() const { return _filter; } + uint8_t sortMode() const { return _sort; } + bool ownPosition(int32_t& lat, int32_t& lon) const { lat = _own_lat; lon = _own_lon; return _own_gps; } + void setSource(Source s) { _source = s; } + void setFilter(uint8_t f) { _filter = f < F_COUNT ? f : F_ALL; } + void setSortMode(uint8_t s) { _sort = s; } + + bool typeMatchesFilter(uint8_t type, uint8_t flags, bool have_flags) const { + switch (_filter) { + case F_FAV: return have_flags && (flags & 0x01); + case F_COMP: return type == ADV_TYPE_CHAT; + case F_RPT: return type == ADV_TYPE_REPEATER; + case F_ROOM: return type == ADV_TYPE_ROOM; + case F_SNSR: return type == ADV_TYPE_SENSOR; + case F_ALL: + default: return true; + } + } + + // ── data refresh ────────────────────────────────────────────────────────── + void refreshStored() { + _count = 0; + _own_lat = _own_lon = 0; + _own_gps = _core && _core->course.currentLocation(_own_lat, _own_lon); + + int nc = the_mesh.getNumContacts(); + for (int i = 0; i < nc && _count < MAX_NEARBY; i++) { + ContactInfo ci; + // getContactByIdx() indexes the RAW contact table, whose first + // MAX_ANON_CONTACTS slots are reserved for anon-request bookkeeping + // (see BaseChatMesh::resetContacts()/ContactsIterator) -- getNumContacts() + // already excludes them from the count, so real contact 0 lives at raw + // index MAX_ANON_CONTACTS, not 0. Reading from 0 pulled those reserved + // (blank, type=ADV_TYPE_NONE) slots into the list as bogus "Unknown" + // rows, and silently dropped the same number of real contacts off the end. + if (!the_mesh.getContactByIdx(i + MAX_ANON_CONTACTS, ci)) continue; + if (!typeMatchesFilter(ci.type, ci.flags, true)) continue; + + Entry& e = _entries[_count++]; + strncpy(e.name, ci.name, sizeof(e.name) - 1); + e.name[sizeof(e.name) - 1] = '\0'; + memcpy(e.pub_key, ci.id.pub_key, PUB_KEY_SIZE); + e.has_key = true; + e.has_prefix = true; + e.lat_e6 = ci.gps_lat; + e.lon_e6 = ci.gps_lon; + bool remote_gps = (ci.gps_lat != 0 || ci.gps_lon != 0); + e.dist_km = (_own_gps && remote_gps) + ? geo::haversineKm(_own_lat, _own_lon, ci.gps_lat, ci.gps_lon) + : -1.0f; + e.type = ci.type; + e.fav = (ci.flags & 0x01) != 0; + e.contact_idx = i + MAX_ANON_CONTACTS; // raw index -- other lookups re-key off this directly + e.lastmod = ci.lastmod; + e.is_known = true; + e.is_live = false; + e.live_verified = false; + } + + mergeLiveTrack(); + mergeRecentlyHeard(); + sortStored(); + } + + void refreshScan() { + static DiscoverResult dr[DISCOVER_RESULTS_MAX]; // scratch -- refresh isn't reentrant + int n = the_mesh.getDiscoverResults(dr, DISCOVER_RESULTS_MAX); + _count = 0; + for (int i = 0; i < n && _count < MAX_NEARBY; i++) { + if (!typeMatchesFilter(dr[i].type, 0, false)) continue; + Entry& e = _entries[_count++]; + strncpy(e.name, dr[i].name, sizeof(e.name) - 1); + e.name[sizeof(e.name) - 1] = '\0'; + e.type = dr[i].type; + memcpy(e.pub_key, dr[i].pub_key, PUB_KEY_SIZE); + e.has_key = true; + e.has_prefix = true; + e.rssi = dr[i].rssi; + e.snr_x4 = dr[i].snr_x4; + e.remote_snr_x4 = dr[i].remote_snr_x4; + e.is_known = dr[i].is_known; + e.fav = false; + e.lat_e6 = e.lon_e6 = 0; + e.dist_km = -1.0f; + e.lastmod = 0; + e.contact_idx = -1; + e.is_live = false; + e.live_verified = false; + } + // strongest first + for (int i = 0; i < _count - 1; i++) { + int best = i; + for (int j = i + 1; j < _count; j++) + if (_entries[j].rssi > _entries[best].rssi) best = j; + if (best != i) { Entry tmp = _entries[i]; _entries[i] = _entries[best]; _entries[best] = tmp; } + } + } + + void refreshModel() { if (_source == SRC_SCAN) refreshScan(); else refreshStored(); } + + // Index of the row for raw contact index `contact_idx`, or of the non-contact + // live row named `name` -- how a screen keeps its selection across a rebuild. + int findContact(int contact_idx) const { + for (int i = 0; i < _count; i++) if (_entries[i].contact_idx == contact_idx) return i; + return -1; + } + int findLiveByName(const char* name) const { + for (int i = 0; i < _count; i++) + if (_entries[i].is_live && strncmp(_entries[i].name, name, sizeof(_entries[i].name) - 1) == 0) return i; + return -1; + } + +protected: + UiCore* _core = nullptr; + NodePrefs* _prefs = nullptr; + + Entry _entries[MAX_NEARBY]; + int _count = 0; + int32_t _own_lat = 0, _own_lon = 0; + bool _own_gps = false; + + Source _source = SRC_STORED; + uint8_t _filter = F_ALL; + uint8_t _sort = SORT_DIST; + + // Overlay live [LOC] shares onto the stored list: refresh a matching contact + // with the fresher shared position, and append senders who aren't contacts so + // a group sharing on a channel still shows up. DM shares match by pubkey + // prefix (verified); channel shares match by name (best-effort). + void mergeLiveTrack() { + if (!_core) return; + LiveTrackStore& lt = _core->live_share.track(); + uint32_t now = rtc_clock.getCurrentTime(); + for (int i = 0; i < LiveTrackStore::CAPACITY; i++) { + if (!lt.isActive(i, now)) continue; + const LiveTrackStore::Entry& s = lt.slotAt(i); + + int m = -1; + for (int j = 0; j < _count; j++) { + if (s.verified && _entries[j].has_key + && memcmp(_entries[j].pub_key, s.key, LiveTrackStore::KEY_LEN) == 0) { m = j; break; } + if (!s.verified && strncmp(_entries[j].name, s.name, sizeof(_entries[j].name) - 1) == 0) { m = j; break; } + } + + if (m >= 0) { + Entry& e = _entries[m]; + // A [LOC] share is an explicit "here I am now", so it defines the pin and + // the distance (used for proximity sorting). Recency for the time sort is + // the most recent of the advert and the share. + e.lat_e6 = s.lat_1e6; + e.lon_e6 = s.lon_1e6; + e.dist_km = _own_gps ? geo::haversineKm(_own_lat, _own_lon, s.lat_1e6, s.lon_1e6) : -1.0f; + if (s.ts > e.lastmod) e.lastmod = s.ts; + e.is_live = true; + e.live_verified = s.verified; + } else if (_count < MAX_NEARBY && typeMatchesFilter(ADV_TYPE_CHAT, 0, false)) { + // A sender we don't have as a contact: treat it as a companion (the only + // node type that shares position), so the type filter still applies. + Entry& e = _entries[_count++]; + memset(&e, 0, sizeof(e)); + strncpy(e.name, s.name, sizeof(e.name) - 1); + e.name[sizeof(e.name) - 1] = '\0'; + // Only a key *prefix* is kept for shares, so Ping and the key view stay + // unavailable for a non-contact live entry; Navigate / waypoint work off + // lat/lon. A DM share's prefix is real (a Locator person target); a + // channel share is matched by name and carries no identity at all. + e.has_key = false; + e.has_prefix = s.verified; + if (s.verified) memcpy(e.pub_key, s.key, LiveTrackStore::KEY_LEN); + e.type = ADV_TYPE_CHAT; + e.lat_e6 = s.lat_1e6; + e.lon_e6 = s.lon_1e6; + e.dist_km = _own_gps ? geo::haversineKm(_own_lat, _own_lon, s.lat_1e6, s.lon_1e6) : -1.0f; + e.lastmod = s.ts; + e.contact_idx = -1; + e.is_known = false; + e.is_live = true; + e.live_verified = s.verified; + } + } + } + + // Fold passively-heard adverts that aren't already listed (contact or live) into + // the list as name+age rows -- no full key/GPS, so informational only. Gated to + // the All filter because AdvertPath carries no node type to filter or sort on. + void mergeRecentlyHeard() { + if (_filter != F_ALL) return; + static AdvertPath heard[8]; // scratch -- refreshStored isn't reentrant + int n = the_mesh.getRecentlyHeard(heard, 8); + for (int i = 0; i < n && _count < MAX_NEARBY; i++) { + AdvertPath& a = heard[i]; + if (a.name[0] == 0) continue; + bool dup = false; + for (int j = 0; j < _count; j++) { + if (_entries[j].has_key && + memcmp(_entries[j].pub_key, a.pubkey_prefix, sizeof(a.pubkey_prefix)) == 0) { dup = true; break; } + if (strncmp(_entries[j].name, a.name, sizeof(a.name) - 1) == 0) { dup = true; break; } + } + if (dup) continue; + Entry& e = _entries[_count++]; + memset(&e, 0, sizeof(e)); + strncpy(e.name, a.name, sizeof(e.name) - 1); + e.name[sizeof(e.name) - 1] = '\0'; + e.type = ADV_TYPE_CHAT; // unknown from AdvertPath -- best-effort label + e.has_key = false; + e.has_prefix = true; + memcpy(e.pub_key, a.pubkey_prefix, sizeof(a.pubkey_prefix)); + e.dist_km = -1.0f; + e.lastmod = a.recv_timestamp; + e.contact_idx = -1; + e.is_known = false; + e.is_live = false; + } + } + + void sortStored() { + uint32_t now_ts = rtc_clock.getCurrentTime(); + const bool fav_first = !(_prefs && _prefs->fav_sort_off); + for (int i = 0; i < _count - 1; i++) { + int best = i; + for (int j = i + 1; j < _count; j++) { + if (fav_first && _entries[j].fav != _entries[best].fav) { + if (_entries[j].fav) best = j; // favourites outrank the time/distance key + continue; + } + if (_sort == SORT_TIME) { + // lastmod=0 or lastmod>now (RTC not synced) → "unknown" → sort to bottom. + uint32_t tj = (_entries[j].lastmod > 0 && now_ts >= _entries[j].lastmod) ? _entries[j].lastmod : 0; + uint32_t tb = (_entries[best].lastmod > 0 && now_ts >= _entries[best].lastmod) ? _entries[best].lastmod : 0; + if (tj > 0 && (tb == 0 || tj > tb)) best = j; // descending — most recent first + } else { + float dj = _entries[j].dist_km, db = _entries[best].dist_km; + if (dj >= 0.0f && (db < 0.0f || dj < db)) best = j; // ascending — closest first + } + } + if (best != i) { Entry tmp = _entries[i]; _entries[i] = _entries[best]; _entries[best] = tmp; } + } + } +}; diff --git a/examples/companion_radio/ui-lvgl/Theme.h b/examples/companion_radio/ui-lvgl/Theme.h index 12299810..d661efa3 100644 --- a/examples/companion_radio/ui-lvgl/Theme.h +++ b/examples/companion_radio/ui-lvgl/Theme.h @@ -28,4 +28,7 @@ namespace theme { #define THEME_FONT_TITLE (&ui_font_16) #define THEME_FONT_LARGE (&ui_font_20) #define THEME_FONT_CLOCK (&ui_font_40) + + // Extra FontAwesome glyphs baked into ui_font_12..20 beyond LVGL's LV_SYMBOL_*. + #define UI_SYMBOL_STAR "\xEF\x80\x85" // U+F005, favourite } diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index 46fe3607..44f53be3 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -7,6 +7,7 @@ #endif #include "../ui-core/UiCore.h" // shared UI Core (header-only, this TU) +#include "../ui-core/NearbyModel.h" #include "Theme.h" #include "LvglPort.h" #include "../ui-core/KeyboardData.h" @@ -87,6 +88,11 @@ void UITask::begin(DisplayDriver* display_drv, SensorManager* sensors, NodePrefs _core = new UiCore(); _core->begin(node_prefs, sensors, this); + _nearby = new NearbyModel(); + _nearby->bindModel(_core, node_prefs); + _scan = new NearbyModel(); + _scan->bindModel(_core, node_prefs); + _scan->setSource(NearbyModel::SRC_SCAN); #ifdef PIN_USER_BTN user_btn.begin(); @@ -141,6 +147,17 @@ void UITask::loop() { refreshStatusBar(); if (_screen == SCR_HOME) refreshHome(); } + if ((_screen == SCR_NEARBY || _screen == SCR_NODE) && (int32_t)(millis() - _next_nearby_ms) >= 0) { + // Scan results trickle in over a few seconds: poll fast while scanning. + _next_nearby_ms = millis() + (_scanning ? 250 : 2000); + if (_scanning && (int32_t)(millis() - _scan_until_ms) >= 0) _scanning = false; + if (_screen == SCR_NEARBY) { + refreshNearbyList(); + if (_scan_overlay) refreshScanPopup(); + } else { + refreshNode(); + } + } if (_screen == SCR_THREAD && (int32_t)(millis() - _next_thread_check_ms) >= 0) { _next_thread_check_ms = millis() + 500; uint32_t sig = threadSignature(); @@ -323,6 +340,9 @@ lv_obj_t* UITask::newScreen(const char* title, bool with_back) { _home_clock = _home_date = _home_unread = nullptr; _thread_list = _compose_ta = _keyboard = nullptr; _header = _body = nullptr; + _nearby_list = _nearby_status = _nearby_sort_lbl = _nearby_chips = nullptr; + _node_info = _node_ping = _node_delete_lbl = nullptr; + _scan_overlay = _scan_list = _scan_status = nullptr; // the popup went with the old screen lv_obj_t* prev = lv_screen_active(); lv_obj_t* scr = lv_obj_create(NULL); styleSurface(scr, theme::BG); @@ -371,6 +391,15 @@ void UITask::back() { case SCR_CONTACTS: showChats(); break; case SCR_SETTINGS: showHome(); break; case SCR_CHATS: showHome(); break; + case SCR_NEARBY: + if (_scan_overlay) closeScanPopup(); + else showHome(); + break; + case SCR_NODE: // back to where the node was picked: the list, or the scan popup over it + _screen = SCR_NEARBY; + buildNearby(); + if (_node_from_scan) showScanPopup(); + break; default: break; } } @@ -380,19 +409,26 @@ void UITask::back() { static void onOpenChats(lv_event_t* e) { (void)e; s_ui->showChats(); } static void onOpenSettings(lv_event_t* e) { (void)e; s_ui->showSettings(); } -// Wide home tile: icon + label left, optional value label right. -static lv_obj_t* homeTile(lv_obj_t* parent, const char* text, lv_event_cb_t cb, lv_obj_t** value_out) { +static void onOpenNearby(lv_event_t* e) { (void)e; s_ui->showNearby(); } + +// Home tile: icon over label, one of a row of three; optional amber value +// (unread count) in its top-right corner. +static lv_obj_t* homeTile(lv_obj_t* parent, const char* icon, const char* text, lv_event_cb_t cb, + lv_obj_t** value_out) { lv_obj_t* tile = lv_button_create(parent); - lv_obj_set_size(tile, LV_PCT(100), 42); + lv_obj_set_height(tile, 64); + lv_obj_set_flex_grow(tile, 1); lv_obj_set_style_bg_color(tile, lv_color_hex(theme::SURFACE), 0); + lv_obj_set_style_bg_color(tile, lv_color_hex(theme::SURFACE_2), LV_STATE_PRESSED); lv_obj_set_style_radius(tile, theme::RADIUS, 0); lv_obj_set_style_shadow_width(tile, 0, 0); + lv_obj_set_style_pad_all(tile, 4, 0); lv_obj_add_event_cb(tile, cb, LV_EVENT_CLICKED, NULL); - lv_obj_t* tl = label(tile, text, THEME_FONT_TITLE, theme::TEXT); - lv_obj_align(tl, LV_ALIGN_LEFT_MID, theme::PAD, 0); + lv_obj_align(label(tile, icon, THEME_FONT_LARGE, theme::TEXT), LV_ALIGN_TOP_MID, 0, 6); + lv_obj_align(label(tile, text, THEME_FONT_SMALL, theme::TEXT_MUTED), LV_ALIGN_BOTTOM_MID, 0, -4); if (value_out) { - *value_out = label(tile, "", THEME_FONT_TITLE, theme::ACCENT); - lv_obj_align(*value_out, LV_ALIGN_RIGHT_MID, -theme::PAD, 0); + *value_out = label(tile, "", THEME_FONT_SMALL, theme::ACCENT); + lv_obj_align(*value_out, LV_ALIGN_TOP_RIGHT, -2, 0); } return tile; } @@ -412,8 +448,15 @@ void UITask::buildHome() { lv_obj_t* name = label(body, the_mesh.getNodeName(), THEME_FONT_BODY, theme::ACCENT); lv_obj_set_style_pad_bottom(name, 4, 0); - homeTile(body, LV_SYMBOL_ENVELOPE " Messages", onOpenChats, &_home_unread); - homeTile(body, LV_SYMBOL_SETTINGS " Settings", onOpenSettings, NULL); + lv_obj_t* tiles = lv_obj_create(body); + styleSurface(tiles, theme::BG); + lv_obj_remove_flag(tiles, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(tiles, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(tiles, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_column(tiles, theme::GAP, 0); + homeTile(tiles, LV_SYMBOL_ENVELOPE, "Messages", onOpenChats, &_home_unread); + homeTile(tiles, LV_SYMBOL_GPS, "Nearby", onOpenNearby, NULL); + homeTile(tiles, LV_SYMBOL_SETTINGS, "Settings", onOpenSettings, NULL); } void UITask::refreshHome() { @@ -433,7 +476,7 @@ void UITask::refreshHome() { lv_label_set_text(_home_date, "time not synced"); } int unread = _core->dmUnreadTotal() + _core->history.getTotalChannelUnread() + _core->roomUnread(); - if (unread > 0) lv_label_set_text_fmt(_home_unread, "%d new", unread); + if (unread > 0) lv_label_set_text_fmt(_home_unread, "%d", unread); else lv_label_set_text(_home_unread, ""); } @@ -565,6 +608,442 @@ void UITask::buildContacts() { if (rows == 0) label(body, "No contacts yet", THEME_FONT_BODY, theme::TEXT_MUTED); } +// ── Nearby ──────────────────────────────────────────────────────────────────── +// Contacts / live shares / heard adverts from NearbyModel, filtered by type +// chips, sorted by distance or recency. Tap a row for detail. Scan (nodes that +// answer a discover request right now) is a popup over the list with its own +// model, since it is a different set: who is in range, not who is known. + +static NearbyModel::Entry s_node; // the node open in SCR_NODE (a copy: the list re-sorts) +enum : uint8_t { NODE_MSG, NODE_PING, NODE_FAV, NODE_ADD, NODE_DELETE }; + +static void onNearbyChip(lv_event_t* e) { s_ui->setNearbyFilter((uint8_t)(uintptr_t)lv_event_get_user_data(e)); } +static void onNearbySort(lv_event_t* e) { (void)e; s_ui->toggleNearbySort(); } +static void onNearbyScan(lv_event_t* e) { (void)e; s_ui->startNearbyScan(); } +static void onNearbyRow(lv_event_t* e) { s_ui->openNode((int)(uintptr_t)lv_event_get_user_data(e)); } +static void onScanRow(lv_event_t* e) { s_ui->openScanNode((int)(uintptr_t)lv_event_get_user_data(e)); } +static void onScanClose(lv_event_t* e) { (void)e; s_ui->closeScanPopup(); } +static void onNodeAction(lv_event_t* e) { s_ui->nodeAction((uint8_t)(uintptr_t)lv_event_get_user_data(e)); } + +static lv_obj_t* headerButton(lv_obj_t* hdr, const char* text, lv_event_cb_t cb, int right, lv_obj_t** label_out) { + lv_obj_t* b = lv_button_create(hdr); + lv_obj_set_size(b, LV_SIZE_CONTENT, 28); + lv_obj_set_style_pad_hor(b, 10, 0); + lv_obj_set_style_pad_ver(b, 0, 0); + lv_obj_set_style_bg_color(b, lv_color_hex(theme::SURFACE), 0); + lv_obj_set_style_bg_color(b, lv_color_hex(theme::SURFACE_2), LV_STATE_PRESSED); + lv_obj_set_style_shadow_width(b, 0, 0); + lv_obj_align(b, LV_ALIGN_RIGHT_MID, -right, 0); + lv_obj_add_event_cb(b, cb, LV_EVENT_CLICKED, NULL); + lv_obj_t* l = label(b, text, THEME_FONT_SMALL, theme::TEXT); + lv_obj_center(l); + if (label_out) *label_out = l; + return b; +} + +void UITask::showNearby() { + _screen = SCR_NEARBY; + _scanning = false; + buildNearby(); // filter / sort persist +} + +void UITask::buildNearby() { + lv_obj_t* body = newScreen("Nearby", true); + lv_obj_set_style_pad_row(body, 4, 0); + if (_header) { + headerButton(_header, LV_SYMBOL_REFRESH " Scan", onNearbyScan, 4, NULL); + headerButton(_header, "", onNearbySort, 84, &_nearby_sort_lbl); + } + + // Type filter chips + _nearby_chips = lv_obj_create(body); + styleSurface(_nearby_chips, theme::BG); + lv_obj_remove_flag(_nearby_chips, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(_nearby_chips, LV_PCT(100), 26); + lv_obj_set_flex_flow(_nearby_chips, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_column(_nearby_chips, 4, 0); + for (uint8_t f = 0; f < NearbyModel::F_COUNT; f++) { + lv_obj_t* c = lv_button_create(_nearby_chips); + lv_obj_set_height(c, 26); + lv_obj_set_flex_grow(c, 1); + lv_obj_set_style_pad_all(c, 0, 0); + lv_obj_set_style_radius(c, 13, 0); + lv_obj_set_style_shadow_width(c, 0, 0); + bool on = f == _nearby->filter(); + lv_obj_set_style_bg_color(c, lv_color_hex(on ? theme::ACCENT : theme::SURFACE), 0); + lv_obj_add_event_cb(c, onNearbyChip, LV_EVENT_CLICKED, (void*)(uintptr_t)f); + lv_obj_center(label(c, NearbyModel::filterLabel(f), THEME_FONT_SMALL, on ? theme::BG : theme::TEXT)); + } + + _nearby_status = label(body, "", THEME_FONT_SMALL, theme::TEXT_MUTED); + + _nearby_list = lv_obj_create(body); + styleSurface(_nearby_list, theme::BG); + lv_obj_set_width(_nearby_list, LV_PCT(100)); + lv_obj_set_flex_grow(_nearby_list, 1); + lv_obj_set_flex_flow(_nearby_list, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_row(_nearby_list, theme::GAP, 0); + lv_obj_set_scrollbar_mode(_nearby_list, LV_SCROLLBAR_MODE_ACTIVE); + + _nearby_sig = 0; + refreshNearbyList(); +} + +void UITask::setNearbyFilter(uint8_t f) { + _nearby->setFilter(f); + buildNearby(); +} + +void UITask::toggleNearbySort() { + _nearby->setSortMode(_nearby->sortMode() == NearbyModel::SORT_DIST ? NearbyModel::SORT_TIME + : NearbyModel::SORT_DIST); + _nearby_sig = 0; + refreshNearbyList(); +} + +void UITask::startNearbyScan() { + _scanning = true; + _scan_until_ms = millis() + 8000; + _next_nearby_ms = millis() + 250; + the_mesh.sendNodeDiscoverReq(); + if (!_scan_overlay) showScanPopup(); + else { _scan_sig = 0; refreshScanPopup(); } +} + +// Dimmed full-screen overlay (swallows taps) holding a panel with the results. +// A child of the current screen, so it goes away with it. +void UITask::showScanPopup() { + _scan_overlay = lv_obj_create(lv_screen_active()); + lv_obj_remove_style_all(_scan_overlay); + lv_obj_set_size(_scan_overlay, LV_PCT(100), LV_PCT(100)); + lv_obj_set_style_bg_color(_scan_overlay, lv_color_hex(0x000000), 0); + lv_obj_set_style_bg_opa(_scan_overlay, LV_OPA_60, 0); + lv_obj_add_flag(_scan_overlay, LV_OBJ_FLAG_CLICKABLE); + lv_obj_remove_flag(_scan_overlay, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t* panel = lv_obj_create(_scan_overlay); + lv_obj_set_size(panel, lv_display_get_horizontal_resolution(NULL) - 16, + lv_display_get_vertical_resolution(NULL) - theme::STATUS_H - 12); + lv_obj_set_pos(panel, 8, theme::STATUS_H + 6); + lv_obj_set_style_bg_color(panel, lv_color_hex(theme::BG), 0); + lv_obj_set_style_border_color(panel, lv_color_hex(theme::ACCENT), 0); + lv_obj_set_style_border_width(panel, 1, 0); + lv_obj_set_style_radius(panel, theme::RADIUS, 0); + lv_obj_set_style_pad_all(panel, theme::PAD, 0); + lv_obj_set_style_pad_row(panel, 4, 0); + lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_COLUMN); + lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t* hdr = lv_obj_create(panel); + styleSurface(hdr, theme::BG); + lv_obj_remove_flag(hdr, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(hdr, LV_PCT(100), 28); + lv_obj_align(label(hdr, "In range now", THEME_FONT_TITLE, theme::TEXT), LV_ALIGN_LEFT_MID, 0, 0); + headerButton(hdr, LV_SYMBOL_CLOSE, onScanClose, 0, NULL); + headerButton(hdr, LV_SYMBOL_REFRESH " Again", onNearbyScan, 44, NULL); + + _scan_status = label(panel, "", THEME_FONT_SMALL, theme::TEXT_MUTED); + _scan_list = lv_obj_create(panel); + styleSurface(_scan_list, theme::BG); + lv_obj_set_width(_scan_list, LV_PCT(100)); + lv_obj_set_flex_grow(_scan_list, 1); + lv_obj_set_flex_flow(_scan_list, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_row(_scan_list, theme::GAP, 0); + lv_obj_set_scrollbar_mode(_scan_list, LV_SCROLLBAR_MODE_ACTIVE); + + _scan_sig = 0; + refreshScanPopup(); +} + +void UITask::closeScanPopup() { + if (_scan_overlay) lv_obj_delete_async(_scan_overlay); // may be closing from its own button + _scan_overlay = _scan_list = _scan_status = nullptr; + _scanning = false; +} + +void UITask::refreshScanPopup() { + if (!_scan_list) return; + _scan->refreshScan(); + int n = _scan->count(); + if (_scanning) lv_label_set_text_fmt(_scan_status, LV_SYMBOL_REFRESH " Listening for replies... %d", n); + else lv_label_set_text_fmt(_scan_status, "%d node%s answered the discover request", n, n == 1 ? "" : "s"); + + uint32_t sig = (uint32_t)n + (_scanning ? 0x10000u : 0); + for (int i = 0; i < n; i++) { + const NearbyModel::Entry& e = _scan->at(i); + sig = sig * 31 + e.rssi * 7 + e.snr_x4 + e.is_known; + for (int k = 0; k < 4; k++) sig = sig * 31 + e.pub_key[k]; + } + if (sig == _scan_sig) return; + _scan_sig = sig; + + lv_obj_clean(_scan_list); + for (int i = 0; i < n; i++) { + const NearbyModel::Entry& e = _scan->at(i); + char title[40], sub[48], right[12]; + if (e.name[0]) snprintf(title, sizeof(title), "%s", e.name); + else snprintf(title, sizeof(title), "%s %02X%02X%02X%02X", NearbyModel::typeName(e.type), + e.pub_key[0], e.pub_key[1], e.pub_key[2], e.pub_key[3]); + snprintf(sub, sizeof(sub), "%s - SNR %.1f / %.1f%s", NearbyModel::typeName(e.type), + e.snr_x4 / 4.0f, e.remote_snr_x4 / 4.0f, e.is_known ? "" : " - new"); + snprintf(right, sizeof(right), "%d dBm", e.rssi); + lv_obj_t* row = listRow(_scan_list, title, sub, onScanRow, (void*)(uintptr_t)i); + lv_obj_align(label(row, right, THEME_FONT_SMALL, theme::TEXT_MUTED), LV_ALIGN_RIGHT_MID, -theme::PAD, 0); + } + if (n == 0) { + lv_obj_t* l = label(_scan_list, _scanning ? "Repeaters and rooms in range will answer." + : "Nobody answered. Try again later or move.", + THEME_FONT_BODY, theme::TEXT_MUTED); + lv_label_set_long_mode(l, LV_LABEL_LONG_WRAP); + lv_obj_set_width(l, LV_PCT(100)); + lv_obj_set_style_pad_top(l, 8, 0); + } +} + +void UITask::openScanNode(int row) { + if (row < 0 || row >= _scan->count()) return; + s_node = _scan->at(row); + _node_from_scan = true; + _scanning = false; + _pinging = false; + _delete_armed_ms = 0; + _screen = SCR_NODE; + buildNode(); +} + +// Changes whenever a rebuild would show something different. +uint32_t UITask::nearbySignature() const { + uint32_t sig = (uint32_t)_nearby->count() * 2654435761u + _nearby->sortMode() + (_scanning ? 7 : 0); + for (int i = 0; i < _nearby->count(); i++) { + const NearbyModel::Entry& e = _nearby->at(i); + sig = sig * 31 + e.contact_idx + (uint32_t)e.lastmod + (uint32_t)(e.dist_km * 100) + e.rssi + e.fav; + for (const char* p = e.name; *p; p++) sig = sig * 31 + (uint8_t)*p; + } + // Ages are shown in minutes, so let the list re-render once a minute anyway. + return sig + rtc_clock.getCurrentTime() / 60; +} + +void UITask::refreshNearbyList() { + if (!_nearby_list) return; + _nearby->refreshModel(); + uint32_t sig = nearbySignature(); + int n = _nearby->count(); + + if (_nearby_sort_lbl) + lv_label_set_text(_nearby_sort_lbl, _nearby->sortMode() == NearbyModel::SORT_TIME ? "Recent" : "Dist"); + int32_t lat, lon; + bool gps = _nearby->ownPosition(lat, lon); + lv_label_set_text_fmt(_nearby_status, "%d node%s%s", n, n == 1 ? "" : "s", + gps ? "" : " - no GPS fix, distances unknown"); + if (sig == _nearby_sig) return; + _nearby_sig = sig; + + int32_t scroll = lv_obj_get_scroll_y(_nearby_list); + lv_obj_clean(_nearby_list); + uint32_t now = rtc_clock.getCurrentTime(); + bool imperial = _prefs && _prefs->units_imperial; + for (int i = 0; i < n; i++) { + const NearbyModel::Entry& e = _nearby->at(i); + char title[48], sub[48], right[16] = ""; + const char* name = e.name[0] ? e.name : "(unknown)"; + snprintf(title, sizeof(title), "%s%s", e.fav ? UI_SYMBOL_STAR " " : "", name); + char age[8]; + geo::fmtAgeShort(age, sizeof(age), now, e.lastmod); + if (e.dist_km >= 0.0f) geo::fmtDist(right, sizeof(right), e.dist_km, imperial); + else if (age[0]) snprintf(right, sizeof(right), "%s", age); + const char* kind = e.contact_idx >= 0 ? NearbyModel::typeName(e.type) : "not a contact"; + snprintf(sub, sizeof(sub), "%s%s%s%s", kind, e.is_live ? " - live" : "", + (e.dist_km >= 0.0f && age[0]) ? " - " : "", (e.dist_km >= 0.0f && age[0]) ? age : ""); + lv_obj_t* row = listRow(_nearby_list, title, sub, onNearbyRow, (void*)(uintptr_t)i); + if (e.fav) lv_obj_set_style_text_color(lv_obj_get_child(row, 0), lv_color_hex(theme::ACCENT), 0); + if (right[0]) { + lv_obj_t* r = label(row, right, THEME_FONT_SMALL, e.is_live ? theme::OK : theme::TEXT_MUTED); + lv_obj_align(r, LV_ALIGN_RIGHT_MID, -theme::PAD, 0); + } + } + if (n == 0) { + lv_obj_t* l = label(_nearby_list, "Nobody here yet. Tap Scan to look around.", + THEME_FONT_BODY, theme::TEXT_MUTED); + lv_obj_set_style_pad_top(l, 12, 0); + } + lv_obj_update_layout(_nearby_list); + lv_obj_scroll_to_y(_nearby_list, scroll, LV_ANIM_OFF); +} + +// ── Node detail ─────────────────────────────────────────────────────────────── + +void UITask::openNode(int row) { + if (row < 0 || row >= _nearby->count()) return; + s_node = _nearby->at(row); + _node_from_scan = false; + _pinging = false; + _delete_armed_ms = 0; + _screen = SCR_NODE; + buildNode(); +} + +static lv_obj_t* actionButton(lv_obj_t* parent, const char* text, uint8_t action, bool accent) { + lv_obj_t* b = lv_button_create(parent); + lv_obj_set_height(b, 40); + lv_obj_set_flex_grow(b, 1); + lv_obj_set_style_pad_hor(b, 4, 0); + lv_obj_set_style_radius(b, theme::RADIUS, 0); + lv_obj_set_style_shadow_width(b, 0, 0); + lv_obj_set_style_bg_color(b, lv_color_hex(accent ? theme::ACCENT_DIM : theme::SURFACE), 0); + lv_obj_set_style_bg_color(b, lv_color_hex(theme::SURFACE_2), LV_STATE_PRESSED); + lv_obj_add_event_cb(b, onNodeAction, LV_EVENT_CLICKED, (void*)(uintptr_t)action); + lv_obj_t* l = label(b, text, THEME_FONT_SMALL, theme::TEXT); + lv_obj_center(l); + return l; +} + +void UITask::buildNode() { + const NearbyModel::Entry& e = s_node; + lv_obj_t* body = newScreen(e.name[0] ? e.name : "(unknown)", true); + + _node_info = label(body, "", THEME_FONT_BODY, theme::TEXT); + lv_label_set_long_mode(_node_info, LV_LABEL_LONG_WRAP); + lv_obj_set_width(_node_info, LV_PCT(100)); + lv_obj_set_style_text_line_space(_node_info, 3, 0); + _node_ping = label(body, "", THEME_FONT_BODY, theme::ACCENT); + + lv_obj_t* spacer = lv_obj_create(body); // pushes the actions to the bottom + lv_obj_remove_style_all(spacer); + lv_obj_set_width(spacer, 1); + lv_obj_set_flex_grow(spacer, 1); + + lv_obj_t* acts = lv_obj_create(body); + styleSurface(acts, theme::BG); + lv_obj_remove_flag(acts, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_size(acts, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(acts, LV_FLEX_FLOW_ROW); + lv_obj_set_style_pad_column(acts, theme::GAP, 0); + bool contact = e.contact_idx >= 0; + if (contact && e.type == ADV_TYPE_CHAT) actionButton(acts, LV_SYMBOL_ENVELOPE " Message", NODE_MSG, true); + if (e.has_key) actionButton(acts, LV_SYMBOL_LOOP " Ping", NODE_PING, false); + if (contact) actionButton(acts, e.fav ? UI_SYMBOL_STAR " Unfav" : UI_SYMBOL_STAR " Fav", NODE_FAV, false); + if (!contact && e.has_key && !e.is_known) actionButton(acts, LV_SYMBOL_PLUS " Add", NODE_ADD, true); + if (contact) _node_delete_lbl = actionButton(acts, LV_SYMBOL_TRASH, NODE_DELETE, false); + + refreshNode(); +} + +void UITask::refreshNode() { + if (!_node_info) return; + const NearbyModel::Entry& e = s_node; + // Refresh position / age / signal from its model while the node is still listed. + NearbyModel* model = _node_from_scan ? _scan : _nearby; + model->refreshModel(); + for (int i = 0; i < model->count(); i++) { + const NearbyModel::Entry& m = model->at(i); + bool same = e.has_key ? (m.has_key && memcmp(m.pub_key, e.pub_key, PUB_KEY_SIZE) == 0) + : (e.contact_idx >= 0) ? m.contact_idx == e.contact_idx + : strncmp(m.name, e.name, sizeof(m.name)) == 0; + if (same) { s_node = m; break; } + } + + char buf[320]; + int o = 0; + // A scan row carries no contact index; is_known says whether it's in the contacts. + bool known = e.contact_idx >= 0 || (_node_from_scan && e.is_known); + o += snprintf(buf + o, sizeof(buf) - o, "%s%s%s%s", NearbyModel::typeName(e.type), + known ? "" : " - not a contact", + e.is_live ? (e.live_verified ? " - live position" : " - live position (channel)") : "", + e.fav ? " - favourite" : ""); + int32_t lat, lon; + bool gps = _nearby->ownPosition(lat, lon); + if (e.lat_e6 != 0 || e.lon_e6 != 0) { + if (gps && e.dist_km >= 0.0f) { + char d[16]; + geo::fmtDist(d, sizeof(d), e.dist_km, _prefs && _prefs->units_imperial); + int az = geo::bearingDeg(lat, lon, e.lat_e6, e.lon_e6); + o += snprintf(buf + o, sizeof(buf) - o, "\n%s %d\xC2\xB0 %s", d, az, geo::bearingCardinal(az)); + } + o += snprintf(buf + o, sizeof(buf) - o, "\n%.5f, %.5f", e.lat_e6 / 1e6, e.lon_e6 / 1e6); + } else if (!_node_from_scan) { + o += snprintf(buf + o, sizeof(buf) - o, "\nNo position shared"); + } + char age[8]; + geo::fmtAgeShort(age, sizeof(age), rtc_clock.getCurrentTime(), e.lastmod); + if (age[0]) o += snprintf(buf + o, sizeof(buf) - o, "\nHeard %s ago", age); + if (_node_from_scan) + o += snprintf(buf + o, sizeof(buf) - o, "\nRSSI %d dBm - SNR %.1f / %.1f dB", e.rssi, + e.snr_x4 / 4.0f, e.remote_snr_x4 / 4.0f); + if (e.has_prefix) + o += snprintf(buf + o, sizeof(buf) - o, "\nID %02X%02X%02X%02X", e.pub_key[0], e.pub_key[1], e.pub_key[2], e.pub_key[3]); + lv_label_set_text(_node_info, buf); + + // Ping result (PingEngine releases its slot on reply; the view owns the timeout) + if (_pinging) { + int16_t out = 0, back = 0; uint32_t rtt = 0; + _core->ping.getResult(out, back, rtt); + if (!_core->ping.isActive() && (out || back || rtt)) { + lv_label_set_text_fmt(_node_ping, "Ping %lu ms - SNR out %.1f back %.1f", (unsigned long)rtt, + out / 4.0f, back / 4.0f); + _pinging = false; + } else if (millis() - _ping_started_ms > 3000) { + _core->ping.clear(); + lv_label_set_text(_node_ping, "Ping: no reply"); + _pinging = false; + } + } + if (_node_delete_lbl && _delete_armed_ms && millis() - _delete_armed_ms > 3000) { + _delete_armed_ms = 0; + lv_label_set_text(_node_delete_lbl, LV_SYMBOL_TRASH); + } +} + +void UITask::nodeAction(uint8_t action) { + NearbyModel::Entry& e = s_node; + switch (action) { + case NODE_MSG: + openDM(e.pub_key); + break; + case NODE_PING: { + if (_pinging) break; + PingEngine::StartResult r = _core->ping.start(e.pub_key); + if (r == PingEngine::STARTED) { + _pinging = true; + _ping_started_ms = millis(); + _next_nearby_ms = millis() + 250; + lv_label_set_text(_node_ping, "Pinging..."); + } else { + lv_label_set_text(_node_ping, r == PingEngine::UNSUPPORTED ? "Ping needs 1-2 byte path hashes" + : "Ping failed"); + } + break; + } + case NODE_FAV: + if (the_mesh.setContactFavourite(e.pub_key, !e.fav)) { + e.fav = !e.fav; + buildNode(); + } + break; + case NODE_ADD: + if (the_mesh.addDiscoveredContact(e.pub_key, e.name, e.type)) { + showToast("Contact added"); + _screen = SCR_NEARBY; + buildNearby(); + } else { + showToast("Contacts full"); + } + break; + case NODE_DELETE: + if (!_delete_armed_ms) { // destructive: second tap within 3 s confirms + _delete_armed_ms = millis(); + if (_delete_armed_ms == 0) _delete_armed_ms = 1; + lv_label_set_text(_node_delete_lbl, "Delete?"); + break; + } + if (the_mesh.deleteContactByKey(e.pub_key)) { + showToast("Contact deleted"); + _screen = SCR_NEARBY; + buildNearby(); + } + break; + } +} + // ── Conversation ────────────────────────────────────────────────────────────── static void onKeyboard(lv_event_t* e) { diff --git a/examples/companion_radio/ui-lvgl/UITask.h b/examples/companion_radio/ui-lvgl/UITask.h index e48a7d5f..ecafd89e 100644 --- a/examples/companion_radio/ui-lvgl/UITask.h +++ b/examples/companion_radio/ui-lvgl/UITask.h @@ -15,6 +15,7 @@ #include "../ui-core/UiCoreHost.h" class UiCore; +class NearbyModel; struct UiEvent; class UITask : public UITaskBase, public UiCoreHost { @@ -37,6 +38,14 @@ public: void showChats(); void showContacts(); void showSettings(); + void showNearby(); + void setNearbyFilter(uint8_t f); + void toggleNearbySort(); + void startNearbyScan(); + void closeScanPopup(); + void openScanNode(int row); + void openNode(int row); + void nodeAction(uint8_t action); void setKeyboardAlphabets(int main_idx, int alt_sel); void openChannel(uint8_t channel_idx); void openDM(const uint8_t* pub_key); @@ -50,7 +59,7 @@ public: void openAdminFor(const ContactInfo& ci, bool from_picker) { (void)ci; (void)from_picker; } private: - enum Screen : uint8_t { SCR_HOME, SCR_CHATS, SCR_CONTACTS, SCR_THREAD, SCR_SETTINGS }; + enum Screen : uint8_t { SCR_HOME, SCR_CHATS, SCR_CONTACTS, SCR_THREAD, SCR_SETTINGS, SCR_NEARBY, SCR_NODE }; void buildStatusBar(); void refreshStatusBar(); @@ -60,6 +69,13 @@ private: void buildChats(); void buildContacts(); void buildSettings(); + void buildNearby(); + void refreshNearbyList(); + uint32_t nearbySignature() const; + void showScanPopup(); + void refreshScanPopup(); + void buildNode(); + void refreshNode(); void buildThread(); void refreshThread(); uint32_t threadSignature() const; @@ -99,4 +115,27 @@ private: lv_obj_t* _thread_list = nullptr; lv_obj_t* _compose_ta = nullptr; lv_obj_t* _keyboard = nullptr; + + // Nearby (SCR_NEARBY) / node detail (SCR_NODE); list data in NearbyModel + NearbyModel* _nearby = nullptr; + NearbyModel* _scan = nullptr; // NODE_DISCOVER results: nodes in range now (popup over the list) + bool _scanning = false; + bool _node_from_scan = false; + uint32_t _scan_sig = 0; + lv_obj_t* _scan_overlay = nullptr; + lv_obj_t* _scan_list = nullptr; + lv_obj_t* _scan_status = nullptr; + uint32_t _scan_until_ms = 0; + uint32_t _nearby_sig = 0; + uint32_t _next_nearby_ms = 0; + bool _pinging = false; + uint32_t _ping_started_ms = 0; + uint32_t _delete_armed_ms = 0; + lv_obj_t* _nearby_list = nullptr; + lv_obj_t* _nearby_status = nullptr; + lv_obj_t* _nearby_sort_lbl = nullptr; + lv_obj_t* _nearby_chips = nullptr; + lv_obj_t* _node_info = nullptr; + lv_obj_t* _node_ping = nullptr; + lv_obj_t* _node_delete_lbl = nullptr; }; diff --git a/examples/companion_radio/ui-lvgl/fonts/generate.sh b/examples/companion_radio/ui-lvgl/fonts/generate.sh index 5ee58180..9092ad31 100755 --- a/examples/companion_radio/ui-lvgl/fonts/generate.sh +++ b/examples/companion_radio/ui-lvgl/fonts/generate.sh @@ -26,8 +26,8 @@ FA_URL=https://raw.githubusercontent.com/lvgl/lvgl/v9.2.2/scripts/built_in_font/ TEXT="0x20-0x7E,0xA0-0x24F,0x370-0x3FF,0x400-0x52F,0x1E80-0x1E85,0x1E9E,0x1EF2-0x1EF3" TEXT="$TEXT,0x2010-0x2027,0x2030,0x2039-0x203A,0x20A4-0x20BF,0x2116,0x2122" # LVGL built-in symbols (same list as lvgl/scripts/built_in_font/built_in_font_gen.py) -# plus 61612 = globe (keyboard script key) -SYMS="61612,61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650" +# plus 61612 = globe (keyboard script key), 61445 = star (favourite) +SYMS="61612,61445,61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650" CONV="npx -y lv_font_conv@1.5.3" gen() { # size weight ranges name diff --git a/examples/companion_radio/ui-lvgl/fonts/ui_font_12.c b/examples/companion_radio/ui-lvgl/fonts/ui_font_12.c index 637cc9d9..11224baf 100644 --- a/examples/companion_radio/ui-lvgl/fonts/ui_font_12.c +++ b/examples/companion_radio/ui-lvgl/fonts/ui_font_12.c @@ -1,7 +1,7 @@ /******************************************************************************* * Size: 12 px * Bpp: 4 - * Opts: --bpp 4 --size 12 --force-fast-kern-format --font /private/tmp/claude-501/-Users-jakub-Documents-MeshCore-MeshCore/3d84fa48-e031-42a4-abec-01b33a2b06ef/scratchpad/fonts/NotoSans-Medium.ttf -r 0x20-0x7E,0xA0-0x24F,0x370-0x3FF,0x400-0x52F,0x1E80-0x1E85,0x1E9E,0x1EF2-0x1EF3,0x2010-0x2027,0x2030,0x2039-0x203A,0x20A4-0x20BF,0x2116,0x2122 --font /private/tmp/claude-501/-Users-jakub-Documents-MeshCore-MeshCore/3d84fa48-e031-42a4-abec-01b33a2b06ef/scratchpad/fonts/fa.woff -r 61612,61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650 --format lvgl --lv-include lvgl.h --lv-font-name ui_font_12 -o /Users/jakub/Documents/MeshCore/MeshCore/examples/companion_radio/ui-lvgl/fonts/ui_font_12.c + * Opts: --bpp 4 --size 12 --force-fast-kern-format --font /var/folders/1d/dgkmygls6x5gsb16pbn2mwlr0000gn/T//meshcore-ui-fonts/NotoSans-Medium.ttf -r 0x20-0x7E,0xA0-0x24F,0x370-0x3FF,0x400-0x52F,0x1E80-0x1E85,0x1E9E,0x1EF2-0x1EF3,0x2010-0x2027,0x2030,0x2039-0x203A,0x20A4-0x20BF,0x2116,0x2122 --font /var/folders/1d/dgkmygls6x5gsb16pbn2mwlr0000gn/T//meshcore-ui-fonts/fa.woff -r 61612,61445,61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650 --format lvgl --lv-include lvgl.h --lv-font-name ui_font_12 -o /Users/jakub/Documents/MeshCore/MeshCore/examples/companion_radio/ui-lvgl/fonts/ui_font_12.c ******************************************************************************/ #ifdef LV_LVGL_H_INCLUDE_SIMPLE @@ -6379,6 +6379,17 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { 0x70, 0x3, 0x20, 0x80, 0x19, 0x0, 0x2e, 0x0, 0x17, 0x6f, 0x4e, 0x32, 0xc3, 0x0, 0x62, 0x10, + /* U+F005 "" */ + 0x0, 0xf2, 0x90, 0x7, 0xfc, 0x75, 0x20, 0x1f, + 0xf4, 0x2, 0x18, 0x7, 0xf1, 0xa0, 0x2, 0x0, + 0x38, 0xa2, 0xb7, 0x0, 0x24, 0xec, 0x95, 0x4, + 0x75, 0x20, 0xe, 0x13, 0x6b, 0x6, 0x60, 0x7, + 0xf0, 0xe0, 0x2, 0x5c, 0x3, 0xe1, 0xc2, 0x0, + 0xa0, 0x40, 0x3d, 0x4, 0x1, 0xc2, 0x1, 0xee, + 0x0, 0xe3, 0x0, 0x84, 0x2, 0x30, 0xe, 0x60, + 0x18, 0xec, 0x40, 0x60, 0xe, 0x49, 0xe7, 0x3, + 0xbc, 0x80, 0x8, + /* U+F008 "" */ 0xb4, 0xdf, 0xff, 0x6a, 0x5a, 0xe1, 0xc6, 0xee, 0x83, 0xc5, 0x28, 0x22, 0xf0, 0x94, 0x11, 0xe1, @@ -7919,66 +7930,67 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { {.bitmap_index = 30941, .adv_w = 196, .box_w = 11, .box_h = 9, .ofs_x = 1, .ofs_y = 0}, {.bitmap_index = 30986, .adv_w = 148, .box_w = 9, .box_h = 5, .ofs_x = 0, .ofs_y = 4}, {.bitmap_index = 31007, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 31063, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 31109, .adv_w = 192, .box_w = 12, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 31159, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 31205, .adv_w = 132, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 31247, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 31320, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 31395, .adv_w = 216, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 31455, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 31509, .adv_w = 216, .box_w = 14, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 31557, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 31630, .adv_w = 96, .box_w = 6, .box_h = 10, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 31653, .adv_w = 144, .box_w = 9, .box_h = 10, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 31688, .adv_w = 216, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 31762, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 31804, .adv_w = 132, .box_w = 9, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 31854, .adv_w = 168, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = -2}, - {.bitmap_index = 31891, .adv_w = 168, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 31937, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 31964, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 31981, .adv_w = 168, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = -2}, - {.bitmap_index = 32019, .adv_w = 168, .box_w = 12, .box_h = 11, .ofs_x = -1, .ofs_y = -1}, - {.bitmap_index = 32066, .adv_w = 120, .box_w = 7, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 32102, .adv_w = 120, .box_w = 7, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 32139, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 32178, .adv_w = 168, .box_w = 11, .box_h = 3, .ofs_x = 0, .ofs_y = 3}, - {.bitmap_index = 32187, .adv_w = 216, .box_w = 14, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 32249, .adv_w = 240, .box_w = 16, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 32340, .adv_w = 216, .box_w = 15, .box_h = 13, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 32415, .adv_w = 192, .box_w = 12, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 32474, .adv_w = 168, .box_w = 11, .box_h = 7, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 32508, .adv_w = 168, .box_w = 11, .box_h = 7, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 32542, .adv_w = 240, .box_w = 16, .box_h = 10, .ofs_x = -1, .ofs_y = 0}, - {.bitmap_index = 32602, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 32622, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 32679, .adv_w = 192, .box_w = 13, .box_h = 13, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 32743, .adv_w = 186, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 32813, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 32874, .adv_w = 168, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 32914, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 32958, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 32990, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 33029, .adv_w = 120, .box_w = 9, .box_h = 13, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 33075, .adv_w = 168, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 33120, .adv_w = 168, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 33173, .adv_w = 216, .box_w = 14, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 33229, .adv_w = 192, .box_w = 14, .box_h = 13, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 33292, .adv_w = 144, .box_w = 9, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 33315, .adv_w = 240, .box_w = 15, .box_h = 12, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 33390, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 33433, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 33482, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 33531, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 33580, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 33622, .adv_w = 240, .box_w = 16, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 33687, .adv_w = 168, .box_w = 10, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 33751, .adv_w = 168, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 33794, .adv_w = 192, .box_w = 13, .box_h = 13, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 33861, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 33916, .adv_w = 144, .box_w = 9, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 33945, .adv_w = 193, .box_w = 13, .box_h = 9, .ofs_x = 0, .ofs_y = 0} + {.bitmap_index = 31063, .adv_w = 216, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31130, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 31176, .adv_w = 192, .box_w = 12, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 31226, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 31272, .adv_w = 132, .box_w = 9, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 31314, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31387, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31462, .adv_w = 216, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 31522, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31576, .adv_w = 216, .box_w = 14, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 31624, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31697, .adv_w = 96, .box_w = 6, .box_h = 10, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 31720, .adv_w = 144, .box_w = 9, .box_h = 10, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 31755, .adv_w = 216, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31829, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 31871, .adv_w = 132, .box_w = 9, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 31921, .adv_w = 168, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 31958, .adv_w = 168, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32004, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32031, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32048, .adv_w = 168, .box_w = 8, .box_h = 12, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 32086, .adv_w = 168, .box_w = 12, .box_h = 11, .ofs_x = -1, .ofs_y = -1}, + {.bitmap_index = 32133, .adv_w = 120, .box_w = 7, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32169, .adv_w = 120, .box_w = 7, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32206, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32245, .adv_w = 168, .box_w = 11, .box_h = 3, .ofs_x = 0, .ofs_y = 3}, + {.bitmap_index = 32254, .adv_w = 216, .box_w = 14, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 32316, .adv_w = 240, .box_w = 16, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32407, .adv_w = 216, .box_w = 15, .box_h = 13, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 32482, .adv_w = 192, .box_w = 12, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32541, .adv_w = 168, .box_w = 11, .box_h = 7, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 32575, .adv_w = 168, .box_w = 11, .box_h = 7, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 32609, .adv_w = 240, .box_w = 16, .box_h = 10, .ofs_x = -1, .ofs_y = 0}, + {.bitmap_index = 32669, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 32689, .adv_w = 192, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32746, .adv_w = 192, .box_w = 13, .box_h = 13, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 32810, .adv_w = 186, .box_w = 12, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32880, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 32941, .adv_w = 168, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 32981, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 33025, .adv_w = 168, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 33057, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 33096, .adv_w = 120, .box_w = 9, .box_h = 13, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 33142, .adv_w = 168, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33187, .adv_w = 168, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33240, .adv_w = 216, .box_w = 14, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 33296, .adv_w = 192, .box_w = 14, .box_h = 13, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 33359, .adv_w = 144, .box_w = 9, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33382, .adv_w = 240, .box_w = 15, .box_h = 12, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 33457, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 33500, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 33549, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 33598, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 33647, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 33689, .adv_w = 240, .box_w = 16, .box_h = 11, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 33754, .adv_w = 168, .box_w = 10, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33818, .adv_w = 168, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 33861, .adv_w = 192, .box_w = 13, .box_h = 13, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 33928, .adv_w = 240, .box_w = 15, .box_h = 9, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 33983, .adv_w = 144, .box_w = 9, .box_h = 13, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 34012, .adv_w = 193, .box_w = 13, .box_h = 9, .ofs_x = 0, .ofs_y = 0} }; /*--------------------- @@ -8002,14 +8014,14 @@ static const uint16_t unicode_list_10[] = { }; static const uint16_t unicode_list_12[] = { - 0x0, 0xc, 0xceeb, 0xcef2, 0xcef5, 0xcef6, 0xcef7, 0xcefb, - 0xcefd, 0xceff, 0xcf03, 0xcf06, 0xcf0b, 0xcf10, 0xcf11, 0xcf12, - 0xcf28, 0xcf2d, 0xcf32, 0xcf35, 0xcf36, 0xcf37, 0xcf3b, 0xcf3c, - 0xcf3d, 0xcf3e, 0xcf51, 0xcf52, 0xcf58, 0xcf5a, 0xcf5b, 0xcf5e, - 0xcf61, 0xcf62, 0xcf63, 0xcf65, 0xcf7d, 0xcf7f, 0xcf96, 0xcfae, - 0xcfaf, 0xcfb1, 0xcfb3, 0xcfca, 0xcfd1, 0xcfd4, 0xcfdd, 0xd006, - 0xd00e, 0xd045, 0xd0d5, 0xd12a, 0xd12b, 0xd12c, 0xd12d, 0xd12e, - 0xd171, 0xd17d, 0xd1d7, 0xd1ee, 0xd444, 0xd6ac, 0xd78c + 0x0, 0xc, 0xceeb, 0xceef, 0xcef2, 0xcef5, 0xcef6, 0xcef7, + 0xcefb, 0xcefd, 0xceff, 0xcf03, 0xcf06, 0xcf0b, 0xcf10, 0xcf11, + 0xcf12, 0xcf28, 0xcf2d, 0xcf32, 0xcf35, 0xcf36, 0xcf37, 0xcf3b, + 0xcf3c, 0xcf3d, 0xcf3e, 0xcf51, 0xcf52, 0xcf58, 0xcf5a, 0xcf5b, + 0xcf5e, 0xcf61, 0xcf62, 0xcf63, 0xcf65, 0xcf7d, 0xcf7f, 0xcf96, + 0xcfae, 0xcfaf, 0xcfb1, 0xcfb3, 0xcfca, 0xcfd1, 0xcfd4, 0xcfdd, + 0xd006, 0xd00e, 0xd045, 0xd0d5, 0xd12a, 0xd12b, 0xd12c, 0xd12d, + 0xd12e, 0xd171, 0xd17d, 0xd1d7, 0xd1ee, 0xd444, 0xd6ac, 0xd78c }; /*Collect the unicode lists and glyph_id offsets*/ @@ -8065,7 +8077,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] = }, { .range_start = 8470, .range_length = 55181, .glyph_id_start = 1017, - .unicode_list = unicode_list_12, .glyph_id_ofs_list = NULL, .list_length = 63, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + .unicode_list = unicode_list_12, .glyph_id_ofs_list = NULL, .list_length = 64, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY } }; @@ -8211,7 +8223,8 @@ static const uint8_t kern_left_class_mapping[] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0 }; /*Map glyph_ids to kern right classes*/ @@ -8351,7 +8364,8 @@ static const uint8_t kern_right_class_mapping[] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0 }; /*Kern values between classes*/ diff --git a/examples/companion_radio/ui-lvgl/fonts/ui_font_14.c b/examples/companion_radio/ui-lvgl/fonts/ui_font_14.c index 76ce09ce..cdab2272 100644 --- a/examples/companion_radio/ui-lvgl/fonts/ui_font_14.c +++ b/examples/companion_radio/ui-lvgl/fonts/ui_font_14.c @@ -1,7 +1,7 @@ /******************************************************************************* * Size: 14 px * Bpp: 4 - * Opts: --bpp 4 --size 14 --force-fast-kern-format --font /private/tmp/claude-501/-Users-jakub-Documents-MeshCore-MeshCore/3d84fa48-e031-42a4-abec-01b33a2b06ef/scratchpad/fonts/NotoSans-Medium.ttf -r 0x20-0x7E,0xA0-0x24F,0x370-0x3FF,0x400-0x52F,0x1E80-0x1E85,0x1E9E,0x1EF2-0x1EF3,0x2010-0x2027,0x2030,0x2039-0x203A,0x20A4-0x20BF,0x2116,0x2122 --font /private/tmp/claude-501/-Users-jakub-Documents-MeshCore-MeshCore/3d84fa48-e031-42a4-abec-01b33a2b06ef/scratchpad/fonts/fa.woff -r 61612,61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650 --format lvgl --lv-include lvgl.h --lv-font-name ui_font_14 -o /Users/jakub/Documents/MeshCore/MeshCore/examples/companion_radio/ui-lvgl/fonts/ui_font_14.c + * Opts: --bpp 4 --size 14 --force-fast-kern-format --font /var/folders/1d/dgkmygls6x5gsb16pbn2mwlr0000gn/T//meshcore-ui-fonts/NotoSans-Medium.ttf -r 0x20-0x7E,0xA0-0x24F,0x370-0x3FF,0x400-0x52F,0x1E80-0x1E85,0x1E9E,0x1EF2-0x1EF3,0x2010-0x2027,0x2030,0x2039-0x203A,0x20A4-0x20BF,0x2116,0x2122 --font /var/folders/1d/dgkmygls6x5gsb16pbn2mwlr0000gn/T//meshcore-ui-fonts/fa.woff -r 61612,61445,61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650 --format lvgl --lv-include lvgl.h --lv-font-name ui_font_14 -o /Users/jakub/Documents/MeshCore/MeshCore/examples/companion_radio/ui-lvgl/fonts/ui_font_14.c ******************************************************************************/ #ifdef LV_LVGL_H_INCLUDE_SIMPLE @@ -7257,6 +7257,19 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { 0xbc, 0x44, 0x1, 0x8, 0x80, 0x2a, 0xfe, 0xd3, 0x1c, 0x42, 0x5d, 0x0, 0xf0, 0x80, 0x0, + /* U+F005 "" */ + 0x0, 0xf8, 0x84, 0x3, 0xff, 0x82, 0x3b, 0xa0, + 0xf, 0xfe, 0xc, 0x81, 0xa0, 0x7, 0xfc, 0x2e, + 0x0, 0x80, 0xf, 0xfa, 0x40, 0x23, 0x50, 0xe, + 0x19, 0xbe, 0xf7, 0x0, 0xd5, 0xfb, 0x6e, 0x8, + 0xc8, 0x20, 0x1f, 0x89, 0x20, 0x55, 0x80, 0x3f, + 0xf8, 0x14, 0x21, 0x2e, 0x1, 0xfe, 0xa5, 0x0, + 0xa1, 0x80, 0x3f, 0x52, 0x80, 0x77, 0x80, 0x7e, + 0x70, 0xf, 0x78, 0x7, 0xe7, 0x0, 0xf2, 0x80, + 0x42, 0x1, 0xb4, 0x3, 0xc4, 0x0, 0x8e, 0xd5, + 0x0, 0x10, 0x7, 0x99, 0xf9, 0xc0, 0xab, 0x5b, + 0xc0, 0x20, + /* U+F008 "" */ 0x50, 0x18, 0x8f, 0xc8, 0xb, 0x55, 0x3a, 0xaf, 0x9e, 0x6a, 0x83, 0xd8, 0x19, 0xbb, 0xc6, 0xfc, @@ -8889,66 +8902,67 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { {.bitmap_index = 37928, .adv_w = 229, .box_w = 13, .box_h = 10, .ofs_x = 1, .ofs_y = 0}, {.bitmap_index = 37984, .adv_w = 173, .box_w = 10, .box_h = 5, .ofs_x = 0, .ofs_y = 5}, {.bitmap_index = 38007, .adv_w = 224, .box_w = 15, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 38078, .adv_w = 224, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 38139, .adv_w = 224, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 38199, .adv_w = 224, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 38260, .adv_w = 154, .box_w = 10, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 38312, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 38402, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 38489, .adv_w = 252, .box_w = 16, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 38569, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 38634, .adv_w = 252, .box_w = 16, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 38697, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 38792, .adv_w = 112, .box_w = 7, .box_h = 12, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 38820, .adv_w = 168, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 38866, .adv_w = 252, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 38956, .adv_w = 224, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 39012, .adv_w = 154, .box_w = 10, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 39072, .adv_w = 196, .box_w = 10, .box_h = 14, .ofs_x = 1, .ofs_y = -2}, - {.bitmap_index = 39119, .adv_w = 196, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 39177, .adv_w = 196, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 39214, .adv_w = 196, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 39239, .adv_w = 196, .box_w = 10, .box_h = 14, .ofs_x = 1, .ofs_y = -2}, - {.bitmap_index = 39288, .adv_w = 196, .box_w = 14, .box_h = 13, .ofs_x = -1, .ofs_y = -1}, - {.bitmap_index = 39347, .adv_w = 140, .box_w = 9, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 39395, .adv_w = 140, .box_w = 9, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 39444, .adv_w = 196, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 39491, .adv_w = 196, .box_w = 13, .box_h = 4, .ofs_x = 0, .ofs_y = 3}, - {.bitmap_index = 39505, .adv_w = 252, .box_w = 16, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 39588, .adv_w = 280, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 39705, .adv_w = 252, .box_w = 17, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 39793, .adv_w = 224, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 39875, .adv_w = 196, .box_w = 13, .box_h = 8, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 39918, .adv_w = 196, .box_w = 13, .box_h = 8, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 39963, .adv_w = 280, .box_w = 18, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 40034, .adv_w = 224, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 40063, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 40135, .adv_w = 224, .box_w = 15, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 40214, .adv_w = 217, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 40307, .adv_w = 196, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 40388, .adv_w = 196, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 40436, .adv_w = 196, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 40492, .adv_w = 196, .box_w = 13, .box_h = 12, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 40532, .adv_w = 224, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 40585, .adv_w = 140, .box_w = 10, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 40641, .adv_w = 196, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 40704, .adv_w = 196, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 40770, .adv_w = 252, .box_w = 16, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 40843, .adv_w = 224, .box_w = 16, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 40920, .adv_w = 168, .box_w = 11, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 40952, .adv_w = 280, .box_w = 18, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 41049, .adv_w = 280, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 41101, .adv_w = 280, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 41156, .adv_w = 280, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 41211, .adv_w = 280, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 41266, .adv_w = 280, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 41315, .adv_w = 280, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 41395, .adv_w = 196, .box_w = 12, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 41479, .adv_w = 196, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 41535, .adv_w = 224, .box_w = 15, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 41612, .adv_w = 280, .box_w = 18, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 41690, .adv_w = 168, .box_w = 11, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 41726, .adv_w = 225, .box_w = 15, .box_h = 10, .ofs_x = 0, .ofs_y = 0} + {.bitmap_index = 38078, .adv_w = 252, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38160, .adv_w = 224, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 38221, .adv_w = 224, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 38281, .adv_w = 224, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 38342, .adv_w = 154, .box_w = 10, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 38394, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38484, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38571, .adv_w = 252, .box_w = 16, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 38651, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38716, .adv_w = 252, .box_w = 16, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 38779, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 38874, .adv_w = 112, .box_w = 7, .box_h = 12, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 38902, .adv_w = 168, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 38948, .adv_w = 252, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 39038, .adv_w = 224, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 39094, .adv_w = 154, .box_w = 10, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 39154, .adv_w = 196, .box_w = 10, .box_h = 14, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 39201, .adv_w = 196, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 39259, .adv_w = 196, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39296, .adv_w = 196, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39321, .adv_w = 196, .box_w = 10, .box_h = 14, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 39370, .adv_w = 196, .box_w = 14, .box_h = 13, .ofs_x = -1, .ofs_y = -1}, + {.bitmap_index = 39429, .adv_w = 140, .box_w = 9, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39477, .adv_w = 140, .box_w = 9, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39526, .adv_w = 196, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39573, .adv_w = 196, .box_w = 13, .box_h = 4, .ofs_x = 0, .ofs_y = 3}, + {.bitmap_index = 39587, .adv_w = 252, .box_w = 16, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 39670, .adv_w = 280, .box_w = 18, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 39787, .adv_w = 252, .box_w = 17, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 39875, .adv_w = 224, .box_w = 14, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 39957, .adv_w = 196, .box_w = 13, .box_h = 8, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 40000, .adv_w = 196, .box_w = 13, .box_h = 8, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 40045, .adv_w = 280, .box_w = 18, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 40116, .adv_w = 224, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 40145, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40217, .adv_w = 224, .box_w = 15, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 40296, .adv_w = 217, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40389, .adv_w = 196, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 40470, .adv_w = 196, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40518, .adv_w = 196, .box_w = 13, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 40574, .adv_w = 196, .box_w = 13, .box_h = 12, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 40614, .adv_w = 224, .box_w = 14, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 40667, .adv_w = 140, .box_w = 10, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 40723, .adv_w = 196, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40786, .adv_w = 196, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 40852, .adv_w = 252, .box_w = 16, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 40925, .adv_w = 224, .box_w = 16, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 41002, .adv_w = 168, .box_w = 11, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41034, .adv_w = 280, .box_w = 18, .box_h = 13, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 41131, .adv_w = 280, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 41183, .adv_w = 280, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 41238, .adv_w = 280, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 41293, .adv_w = 280, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 41348, .adv_w = 280, .box_w = 18, .box_h = 10, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 41397, .adv_w = 280, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 41477, .adv_w = 196, .box_w = 12, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41561, .adv_w = 196, .box_w = 13, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41617, .adv_w = 224, .box_w = 15, .box_h = 15, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 41694, .adv_w = 280, .box_w = 18, .box_h = 11, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 41772, .adv_w = 168, .box_w = 11, .box_h = 15, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 41808, .adv_w = 225, .box_w = 15, .box_h = 10, .ofs_x = 0, .ofs_y = 0} }; /*--------------------- @@ -8972,14 +8986,14 @@ static const uint16_t unicode_list_10[] = { }; static const uint16_t unicode_list_12[] = { - 0x0, 0xc, 0xceeb, 0xcef2, 0xcef5, 0xcef6, 0xcef7, 0xcefb, - 0xcefd, 0xceff, 0xcf03, 0xcf06, 0xcf0b, 0xcf10, 0xcf11, 0xcf12, - 0xcf28, 0xcf2d, 0xcf32, 0xcf35, 0xcf36, 0xcf37, 0xcf3b, 0xcf3c, - 0xcf3d, 0xcf3e, 0xcf51, 0xcf52, 0xcf58, 0xcf5a, 0xcf5b, 0xcf5e, - 0xcf61, 0xcf62, 0xcf63, 0xcf65, 0xcf7d, 0xcf7f, 0xcf96, 0xcfae, - 0xcfaf, 0xcfb1, 0xcfb3, 0xcfca, 0xcfd1, 0xcfd4, 0xcfdd, 0xd006, - 0xd00e, 0xd045, 0xd0d5, 0xd12a, 0xd12b, 0xd12c, 0xd12d, 0xd12e, - 0xd171, 0xd17d, 0xd1d7, 0xd1ee, 0xd444, 0xd6ac, 0xd78c + 0x0, 0xc, 0xceeb, 0xceef, 0xcef2, 0xcef5, 0xcef6, 0xcef7, + 0xcefb, 0xcefd, 0xceff, 0xcf03, 0xcf06, 0xcf0b, 0xcf10, 0xcf11, + 0xcf12, 0xcf28, 0xcf2d, 0xcf32, 0xcf35, 0xcf36, 0xcf37, 0xcf3b, + 0xcf3c, 0xcf3d, 0xcf3e, 0xcf51, 0xcf52, 0xcf58, 0xcf5a, 0xcf5b, + 0xcf5e, 0xcf61, 0xcf62, 0xcf63, 0xcf65, 0xcf7d, 0xcf7f, 0xcf96, + 0xcfae, 0xcfaf, 0xcfb1, 0xcfb3, 0xcfca, 0xcfd1, 0xcfd4, 0xcfdd, + 0xd006, 0xd00e, 0xd045, 0xd0d5, 0xd12a, 0xd12b, 0xd12c, 0xd12d, + 0xd12e, 0xd171, 0xd17d, 0xd1d7, 0xd1ee, 0xd444, 0xd6ac, 0xd78c }; /*Collect the unicode lists and glyph_id offsets*/ @@ -9035,7 +9049,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] = }, { .range_start = 8470, .range_length = 55181, .glyph_id_start = 1017, - .unicode_list = unicode_list_12, .glyph_id_ofs_list = NULL, .list_length = 63, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + .unicode_list = unicode_list_12, .glyph_id_ofs_list = NULL, .list_length = 64, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY } }; @@ -9181,7 +9195,8 @@ static const uint8_t kern_left_class_mapping[] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0 }; /*Map glyph_ids to kern right classes*/ @@ -9321,7 +9336,8 @@ static const uint8_t kern_right_class_mapping[] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0 }; /*Kern values between classes*/ diff --git a/examples/companion_radio/ui-lvgl/fonts/ui_font_16.c b/examples/companion_radio/ui-lvgl/fonts/ui_font_16.c index 7f0c1ce8..37fe27c0 100644 --- a/examples/companion_radio/ui-lvgl/fonts/ui_font_16.c +++ b/examples/companion_radio/ui-lvgl/fonts/ui_font_16.c @@ -1,7 +1,7 @@ /******************************************************************************* * Size: 16 px * Bpp: 4 - * Opts: --bpp 4 --size 16 --force-fast-kern-format --font /private/tmp/claude-501/-Users-jakub-Documents-MeshCore-MeshCore/3d84fa48-e031-42a4-abec-01b33a2b06ef/scratchpad/fonts/NotoSans-Medium.ttf -r 0x20-0x7E,0xA0-0x24F,0x370-0x3FF,0x400-0x52F,0x1E80-0x1E85,0x1E9E,0x1EF2-0x1EF3,0x2010-0x2027,0x2030,0x2039-0x203A,0x20A4-0x20BF,0x2116,0x2122 --font /private/tmp/claude-501/-Users-jakub-Documents-MeshCore-MeshCore/3d84fa48-e031-42a4-abec-01b33a2b06ef/scratchpad/fonts/fa.woff -r 61612,61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650 --format lvgl --lv-include lvgl.h --lv-font-name ui_font_16 -o /Users/jakub/Documents/MeshCore/MeshCore/examples/companion_radio/ui-lvgl/fonts/ui_font_16.c + * Opts: --bpp 4 --size 16 --force-fast-kern-format --font /var/folders/1d/dgkmygls6x5gsb16pbn2mwlr0000gn/T//meshcore-ui-fonts/NotoSans-Medium.ttf -r 0x20-0x7E,0xA0-0x24F,0x370-0x3FF,0x400-0x52F,0x1E80-0x1E85,0x1E9E,0x1EF2-0x1EF3,0x2010-0x2027,0x2030,0x2039-0x203A,0x20A4-0x20BF,0x2116,0x2122 --font /var/folders/1d/dgkmygls6x5gsb16pbn2mwlr0000gn/T//meshcore-ui-fonts/fa.woff -r 61612,61445,61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650 --format lvgl --lv-include lvgl.h --lv-font-name ui_font_16 -o /Users/jakub/Documents/MeshCore/MeshCore/examples/companion_radio/ui-lvgl/fonts/ui_font_16.c ******************************************************************************/ #ifdef LV_LVGL_H_INCLUDE_SIMPLE @@ -8308,6 +8308,21 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { 0x0, 0x62, 0xbf, 0xf5, 0x97, 0xa0, 0x1, 0x3c, 0x3, 0xfc, + /* U+F005 "" */ + 0x0, 0xfd, 0x98, 0x0, 0xff, 0xe1, 0xa9, 0x99, + 0x40, 0x3f, 0xf8, 0x50, 0x0, 0x80, 0xf, 0xfe, + 0xa, 0x90, 0x0, 0x94, 0x3, 0xfc, 0x27, 0x0, + 0x1a, 0xc, 0x40, 0x30, 0xce, 0x77, 0x30, 0x80, + 0x31, 0x67, 0x73, 0x24, 0x55, 0x8c, 0x40, 0x3f, + 0xc2, 0x6c, 0xa8, 0xc0, 0x1f, 0xfc, 0x26, 0x40, + 0x96, 0x0, 0xff, 0xe0, 0x34, 0x80, 0x52, 0xe0, + 0x1f, 0xe7, 0x90, 0xe, 0x82, 0x0, 0xfc, 0x50, + 0x1, 0xf1, 0x0, 0x7e, 0x20, 0xf, 0x88, 0x3, + 0xfc, 0x40, 0x1e, 0x60, 0xf, 0xf3, 0x0, 0x78, + 0xc0, 0x27, 0xee, 0x38, 0x4, 0x60, 0x1e, 0x21, + 0x6e, 0x81, 0x14, 0x73, 0x9, 0x0, 0x79, 0x7a, + 0x44, 0x3, 0xc, 0xf2, 0x80, 0x40, + /* U+F008 "" */ 0xd0, 0x7, 0xff, 0xf8, 0x1, 0xa5, 0xfe, 0x3, 0x77, 0xf1, 0x87, 0xf8, 0x83, 0xfc, 0x9, 0x11, @@ -9993,66 +10008,67 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { {.bitmap_index = 46302, .adv_w = 262, .box_w = 15, .box_h = 12, .ofs_x = 1, .ofs_y = 0}, {.bitmap_index = 46374, .adv_w = 198, .box_w = 12, .box_h = 7, .ofs_x = 0, .ofs_y = 5}, {.bitmap_index = 46410, .adv_w = 256, .box_w = 16, .box_h = 17, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 46484, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 46548, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 46616, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 46689, .adv_w = 176, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 46753, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 46862, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 46964, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 47055, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 47123, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 47186, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 47290, .adv_w = 128, .box_w = 8, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 47321, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 47372, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 47479, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 47546, .adv_w = 176, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 47613, .adv_w = 224, .box_w = 10, .box_h = 16, .ofs_x = 2, .ofs_y = -2}, - {.bitmap_index = 47665, .adv_w = 224, .box_w = 14, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 47737, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 47766, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 47786, .adv_w = 224, .box_w = 10, .box_h = 16, .ofs_x = 2, .ofs_y = -2}, - {.bitmap_index = 47838, .adv_w = 224, .box_w = 16, .box_h = 14, .ofs_x = -1, .ofs_y = -1}, - {.bitmap_index = 47909, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 47966, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 48023, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 48068, .adv_w = 224, .box_w = 14, .box_h = 4, .ofs_x = 0, .ofs_y = 4}, - {.bitmap_index = 48085, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 48184, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 48318, .adv_w = 288, .box_w = 20, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 48430, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 48530, .adv_w = 224, .box_w = 14, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 48585, .adv_w = 224, .box_w = 14, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 48642, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 48730, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 48759, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 48827, .adv_w = 256, .box_w = 17, .box_h = 17, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 48921, .adv_w = 248, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 49029, .adv_w = 224, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 49120, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 49167, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 49219, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 49274, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 49333, .adv_w = 160, .box_w = 12, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 49401, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 49462, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 49532, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 49598, .adv_w = 256, .box_w = 18, .box_h = 18, .ofs_x = -1, .ofs_y = -3}, - {.bitmap_index = 49687, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 49720, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 49833, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 49878, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 49922, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 49966, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 50012, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 50052, .adv_w = 320, .box_w = 21, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 50153, .adv_w = 224, .box_w = 12, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, - {.bitmap_index = 50249, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 50304, .adv_w = 256, .box_w = 17, .box_h = 17, .ofs_x = -1, .ofs_y = -3}, - {.bitmap_index = 50398, .adv_w = 320, .box_w = 20, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 50487, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 50526, .adv_w = 258, .box_w = 17, .box_h = 11, .ofs_x = 0, .ofs_y = 1} + {.bitmap_index = 46484, .adv_w = 288, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 46586, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 46650, .adv_w = 256, .box_w = 16, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 46718, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 46791, .adv_w = 176, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 46855, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 46964, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47066, .adv_w = 288, .box_w = 18, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 47157, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47225, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 47288, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47392, .adv_w = 128, .box_w = 8, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 47423, .adv_w = 192, .box_w = 12, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 47474, .adv_w = 288, .box_w = 18, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47581, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 47648, .adv_w = 176, .box_w = 11, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 47715, .adv_w = 224, .box_w = 10, .box_h = 16, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 47767, .adv_w = 224, .box_w = 14, .box_h = 18, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 47839, .adv_w = 224, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 47868, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 47888, .adv_w = 224, .box_w = 10, .box_h = 16, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 47940, .adv_w = 224, .box_w = 16, .box_h = 14, .ofs_x = -1, .ofs_y = -1}, + {.bitmap_index = 48011, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48068, .adv_w = 160, .box_w = 10, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48125, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48170, .adv_w = 224, .box_w = 14, .box_h = 4, .ofs_x = 0, .ofs_y = 4}, + {.bitmap_index = 48187, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 48286, .adv_w = 320, .box_w = 20, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48420, .adv_w = 288, .box_w = 20, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 48532, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48632, .adv_w = 224, .box_w = 14, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 48687, .adv_w = 224, .box_w = 14, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 48744, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 48832, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 48861, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 48929, .adv_w = 256, .box_w = 17, .box_h = 17, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 49023, .adv_w = 248, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49131, .adv_w = 224, .box_w = 15, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 49222, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49269, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 49321, .adv_w = 224, .box_w = 14, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 49376, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 49435, .adv_w = 160, .box_w = 12, .box_h = 16, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 49503, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49564, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49634, .adv_w = 288, .box_w = 18, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 49700, .adv_w = 256, .box_w = 18, .box_h = 18, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 49789, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 49822, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 49935, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 49980, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 50024, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 50068, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 50114, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 50154, .adv_w = 320, .box_w = 21, .box_h = 14, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 50255, .adv_w = 224, .box_w = 12, .box_h = 16, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 50351, .adv_w = 224, .box_w = 14, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50406, .adv_w = 256, .box_w = 17, .box_h = 17, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 50500, .adv_w = 320, .box_w = 20, .box_h = 12, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 50589, .adv_w = 192, .box_w = 12, .box_h = 16, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 50628, .adv_w = 258, .box_w = 17, .box_h = 11, .ofs_x = 0, .ofs_y = 1} }; /*--------------------- @@ -10076,14 +10092,14 @@ static const uint16_t unicode_list_10[] = { }; static const uint16_t unicode_list_12[] = { - 0x0, 0xc, 0xceeb, 0xcef2, 0xcef5, 0xcef6, 0xcef7, 0xcefb, - 0xcefd, 0xceff, 0xcf03, 0xcf06, 0xcf0b, 0xcf10, 0xcf11, 0xcf12, - 0xcf28, 0xcf2d, 0xcf32, 0xcf35, 0xcf36, 0xcf37, 0xcf3b, 0xcf3c, - 0xcf3d, 0xcf3e, 0xcf51, 0xcf52, 0xcf58, 0xcf5a, 0xcf5b, 0xcf5e, - 0xcf61, 0xcf62, 0xcf63, 0xcf65, 0xcf7d, 0xcf7f, 0xcf96, 0xcfae, - 0xcfaf, 0xcfb1, 0xcfb3, 0xcfca, 0xcfd1, 0xcfd4, 0xcfdd, 0xd006, - 0xd00e, 0xd045, 0xd0d5, 0xd12a, 0xd12b, 0xd12c, 0xd12d, 0xd12e, - 0xd171, 0xd17d, 0xd1d7, 0xd1ee, 0xd444, 0xd6ac, 0xd78c + 0x0, 0xc, 0xceeb, 0xceef, 0xcef2, 0xcef5, 0xcef6, 0xcef7, + 0xcefb, 0xcefd, 0xceff, 0xcf03, 0xcf06, 0xcf0b, 0xcf10, 0xcf11, + 0xcf12, 0xcf28, 0xcf2d, 0xcf32, 0xcf35, 0xcf36, 0xcf37, 0xcf3b, + 0xcf3c, 0xcf3d, 0xcf3e, 0xcf51, 0xcf52, 0xcf58, 0xcf5a, 0xcf5b, + 0xcf5e, 0xcf61, 0xcf62, 0xcf63, 0xcf65, 0xcf7d, 0xcf7f, 0xcf96, + 0xcfae, 0xcfaf, 0xcfb1, 0xcfb3, 0xcfca, 0xcfd1, 0xcfd4, 0xcfdd, + 0xd006, 0xd00e, 0xd045, 0xd0d5, 0xd12a, 0xd12b, 0xd12c, 0xd12d, + 0xd12e, 0xd171, 0xd17d, 0xd1d7, 0xd1ee, 0xd444, 0xd6ac, 0xd78c }; /*Collect the unicode lists and glyph_id offsets*/ @@ -10139,7 +10155,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] = }, { .range_start = 8470, .range_length = 55181, .glyph_id_start = 1017, - .unicode_list = unicode_list_12, .glyph_id_ofs_list = NULL, .list_length = 63, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + .unicode_list = unicode_list_12, .glyph_id_ofs_list = NULL, .list_length = 64, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY } }; @@ -10285,7 +10301,8 @@ static const uint8_t kern_left_class_mapping[] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0 }; /*Map glyph_ids to kern right classes*/ @@ -10425,7 +10442,8 @@ static const uint8_t kern_right_class_mapping[] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0 }; /*Kern values between classes*/ diff --git a/examples/companion_radio/ui-lvgl/fonts/ui_font_20.c b/examples/companion_radio/ui-lvgl/fonts/ui_font_20.c index 2f7c0763..dc404658 100644 --- a/examples/companion_radio/ui-lvgl/fonts/ui_font_20.c +++ b/examples/companion_radio/ui-lvgl/fonts/ui_font_20.c @@ -1,7 +1,7 @@ /******************************************************************************* * Size: 20 px * Bpp: 4 - * Opts: --bpp 4 --size 20 --force-fast-kern-format --font /private/tmp/claude-501/-Users-jakub-Documents-MeshCore-MeshCore/3d84fa48-e031-42a4-abec-01b33a2b06ef/scratchpad/fonts/NotoSans-Medium.ttf -r 0x20-0x7E,0xA0-0x24F,0x370-0x3FF,0x400-0x52F,0x1E80-0x1E85,0x1E9E,0x1EF2-0x1EF3,0x2010-0x2027,0x2030,0x2039-0x203A,0x20A4-0x20BF,0x2116,0x2122 --font /private/tmp/claude-501/-Users-jakub-Documents-MeshCore-MeshCore/3d84fa48-e031-42a4-abec-01b33a2b06ef/scratchpad/fonts/fa.woff -r 61612,61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650 --format lvgl --lv-include lvgl.h --lv-font-name ui_font_20 -o /Users/jakub/Documents/MeshCore/MeshCore/examples/companion_radio/ui-lvgl/fonts/ui_font_20.c + * Opts: --bpp 4 --size 20 --force-fast-kern-format --font /var/folders/1d/dgkmygls6x5gsb16pbn2mwlr0000gn/T//meshcore-ui-fonts/NotoSans-Medium.ttf -r 0x20-0x7E,0xA0-0x24F,0x370-0x3FF,0x400-0x52F,0x1E80-0x1E85,0x1E9E,0x1EF2-0x1EF3,0x2010-0x2027,0x2030,0x2039-0x203A,0x20A4-0x20BF,0x2116,0x2122 --font /var/folders/1d/dgkmygls6x5gsb16pbn2mwlr0000gn/T//meshcore-ui-fonts/fa.woff -r 61612,61445,61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650 --format lvgl --lv-include lvgl.h --lv-font-name ui_font_20 -o /Users/jakub/Documents/MeshCore/MeshCore/examples/companion_radio/ui-lvgl/fonts/ui_font_20.c ******************************************************************************/ #ifdef LV_LVGL_H_INCLUDE_SIMPLE @@ -10406,6 +10406,25 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = { 0x1, 0xc4, 0xc0, 0x1f, 0x11, 0x0, 0x27, 0xd7, 0x55, 0x4e, 0x88, 0x7, 0xff, 0x0, + /* U+F005 "" */ + 0x0, 0xff, 0x1b, 0x0, 0x7f, 0xf1, 0xc7, 0x26, + 0x0, 0x3f, 0xf8, 0xd2, 0x0, 0x71, 0x0, 0xff, + 0xe2, 0xb, 0x80, 0x52, 0x1, 0xff, 0xc4, 0x90, + 0xc, 0xe2, 0x1, 0xff, 0xc3, 0x70, 0xe, 0x60, + 0xf, 0xf8, 0x95, 0xec, 0x3, 0xd1, 0x6, 0x41, + 0x0, 0xd3, 0xbf, 0xb5, 0x8, 0x1, 0xf3, 0xcd, + 0xf7, 0xe1, 0x93, 0x10, 0x7, 0xff, 0x14, 0xe4, + 0x94, 0x3, 0xff, 0x8c, 0x32, 0x14, 0xc0, 0x1f, + 0xfc, 0x41, 0xd3, 0x0, 0x4b, 0x0, 0x7f, 0xf0, + 0x87, 0xc, 0x3, 0x4b, 0x80, 0x7f, 0xf0, 0x7, + 0x8, 0x3, 0xd0, 0x60, 0x1f, 0xf5, 0x10, 0x7, + 0xe2, 0x0, 0xff, 0x88, 0x3, 0xf8, 0x40, 0x3f, + 0xee, 0x0, 0xfc, 0x60, 0x1f, 0xfc, 0x3, 0x0, + 0xfc, 0xa0, 0x1e, 0x20, 0xe, 0x50, 0xf, 0xde, + 0x1, 0x9b, 0xb6, 0x88, 0x2, 0x11, 0x0, 0x7f, + 0x97, 0x64, 0x41, 0x76, 0x88, 0x3, 0xfd, 0x10, + 0xda, 0x20, 0xe, 0x5d, 0xab, 0x10, 0x8, + /* U+F008 "" */ 0xc4, 0x3, 0xff, 0xff, 0xe0, 0x20, 0x26, 0x1c, 0xe7, 0x0, 0x4, 0x7f, 0x0, 0x27, 0x24, 0xc1, @@ -12334,66 +12353,67 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = { {.bitmap_index = 62808, .adv_w = 327, .box_w = 19, .box_h = 15, .ofs_x = 1, .ofs_y = 0}, {.bitmap_index = 62909, .adv_w = 247, .box_w = 14, .box_h = 8, .ofs_x = 0, .ofs_y = 7}, {.bitmap_index = 62952, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 63062, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 63174, .adv_w = 320, .box_w = 20, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 63261, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 63361, .adv_w = 220, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 63452, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 63611, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 63764, .adv_w = 360, .box_w = 23, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 63907, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 64011, .adv_w = 360, .box_w = 23, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 64108, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 64263, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 64301, .adv_w = 240, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 64375, .adv_w = 360, .box_w = 23, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 64529, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 64624, .adv_w = 220, .box_w = 14, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 64727, .adv_w = 280, .box_w = 13, .box_h = 19, .ofs_x = 2, .ofs_y = -2}, - {.bitmap_index = 64795, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 64897, .adv_w = 280, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 64954, .adv_w = 280, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 64995, .adv_w = 280, .box_w = 13, .box_h = 19, .ofs_x = 2, .ofs_y = -2}, - {.bitmap_index = 65069, .adv_w = 280, .box_w = 19, .box_h = 19, .ofs_x = -1, .ofs_y = -2}, - {.bitmap_index = 65168, .adv_w = 200, .box_w = 11, .box_h = 19, .ofs_x = 1, .ofs_y = -2}, - {.bitmap_index = 65253, .adv_w = 200, .box_w = 11, .box_h = 19, .ofs_x = 1, .ofs_y = -2}, - {.bitmap_index = 65334, .adv_w = 280, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 65406, .adv_w = 280, .box_w = 18, .box_h = 5, .ofs_x = 0, .ofs_y = 5}, - {.bitmap_index = 65428, .adv_w = 360, .box_w = 23, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 65578, .adv_w = 400, .box_w = 26, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, - {.bitmap_index = 65781, .adv_w = 360, .box_w = 24, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, - {.bitmap_index = 65934, .adv_w = 320, .box_w = 20, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 66076, .adv_w = 280, .box_w = 18, .box_h = 11, .ofs_x = 0, .ofs_y = 2}, - {.bitmap_index = 66152, .adv_w = 280, .box_w = 18, .box_h = 11, .ofs_x = 0, .ofs_y = 2}, - {.bitmap_index = 66230, .adv_w = 400, .box_w = 26, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 66348, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 66392, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 66502, .adv_w = 320, .box_w = 21, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, - {.bitmap_index = 66640, .adv_w = 310, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 66799, .adv_w = 280, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 66947, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 67020, .adv_w = 280, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 67113, .adv_w = 280, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 67173, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 67263, .adv_w = 200, .box_w = 14, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, - {.bitmap_index = 67356, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 67444, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 67555, .adv_w = 360, .box_w = 23, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 67681, .adv_w = 320, .box_w = 22, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, - {.bitmap_index = 67802, .adv_w = 240, .box_w = 15, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 67850, .adv_w = 400, .box_w = 25, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, - {.bitmap_index = 68014, .adv_w = 400, .box_w = 25, .box_h = 13, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 68081, .adv_w = 400, .box_w = 25, .box_h = 13, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 63062, .adv_w = 360, .box_w = 22, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 63197, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 63309, .adv_w = 320, .box_w = 20, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 63396, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 63496, .adv_w = 220, .box_w = 14, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 63587, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 63746, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 63899, .adv_w = 360, .box_w = 23, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 64042, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 64146, .adv_w = 360, .box_w = 23, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 64243, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 64398, .adv_w = 160, .box_w = 10, .box_h = 16, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 64436, .adv_w = 240, .box_w = 15, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 64510, .adv_w = 360, .box_w = 23, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 64664, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 64759, .adv_w = 220, .box_w = 14, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 64862, .adv_w = 280, .box_w = 13, .box_h = 19, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 64930, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 65032, .adv_w = 280, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 65089, .adv_w = 280, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 65130, .adv_w = 280, .box_w = 13, .box_h = 19, .ofs_x = 2, .ofs_y = -2}, + {.bitmap_index = 65204, .adv_w = 280, .box_w = 19, .box_h = 19, .ofs_x = -1, .ofs_y = -2}, + {.bitmap_index = 65303, .adv_w = 200, .box_w = 11, .box_h = 19, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 65388, .adv_w = 200, .box_w = 11, .box_h = 19, .ofs_x = 1, .ofs_y = -2}, + {.bitmap_index = 65469, .adv_w = 280, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 65541, .adv_w = 280, .box_w = 18, .box_h = 5, .ofs_x = 0, .ofs_y = 5}, + {.bitmap_index = 65563, .adv_w = 360, .box_w = 23, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 65713, .adv_w = 400, .box_w = 26, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 65916, .adv_w = 360, .box_w = 24, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 66069, .adv_w = 320, .box_w = 20, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 66211, .adv_w = 280, .box_w = 18, .box_h = 11, .ofs_x = 0, .ofs_y = 2}, + {.bitmap_index = 66287, .adv_w = 280, .box_w = 18, .box_h = 11, .ofs_x = 0, .ofs_y = 2}, + {.bitmap_index = 66365, .adv_w = 400, .box_w = 26, .box_h = 16, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 66483, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 66527, .adv_w = 320, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 66637, .adv_w = 320, .box_w = 21, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 66775, .adv_w = 310, .box_w = 20, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 66934, .adv_w = 280, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 67082, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 67155, .adv_w = 280, .box_w = 18, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, + {.bitmap_index = 67248, .adv_w = 280, .box_w = 18, .box_h = 17, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 67308, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 67398, .adv_w = 200, .box_w = 14, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 67491, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 67579, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 67690, .adv_w = 360, .box_w = 23, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 67816, .adv_w = 320, .box_w = 22, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 67937, .adv_w = 240, .box_w = 15, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 67985, .adv_w = 400, .box_w = 25, .box_h = 19, .ofs_x = 0, .ofs_y = -2}, {.bitmap_index = 68149, .adv_w = 400, .box_w = 25, .box_h = 13, .ofs_x = 0, .ofs_y = 1}, {.bitmap_index = 68216, .adv_w = 400, .box_w = 25, .box_h = 13, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 68283, .adv_w = 400, .box_w = 25, .box_h = 13, .ofs_x = 0, .ofs_y = 1}, - {.bitmap_index = 68343, .adv_w = 400, .box_w = 26, .box_h = 17, .ofs_x = 0, .ofs_y = -1}, - {.bitmap_index = 68477, .adv_w = 280, .box_w = 16, .box_h = 21, .ofs_x = 1, .ofs_y = -3}, - {.bitmap_index = 68624, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 68711, .adv_w = 320, .box_w = 21, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, - {.bitmap_index = 68851, .adv_w = 400, .box_w = 25, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, - {.bitmap_index = 68971, .adv_w = 240, .box_w = 15, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, - {.bitmap_index = 69030, .adv_w = 322, .box_w = 21, .box_h = 13, .ofs_x = 0, .ofs_y = 1} + {.bitmap_index = 68284, .adv_w = 400, .box_w = 25, .box_h = 13, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 68351, .adv_w = 400, .box_w = 25, .box_h = 13, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 68418, .adv_w = 400, .box_w = 25, .box_h = 13, .ofs_x = 0, .ofs_y = 1}, + {.bitmap_index = 68478, .adv_w = 400, .box_w = 26, .box_h = 17, .ofs_x = 0, .ofs_y = -1}, + {.bitmap_index = 68612, .adv_w = 280, .box_w = 16, .box_h = 21, .ofs_x = 1, .ofs_y = -3}, + {.bitmap_index = 68759, .adv_w = 280, .box_w = 18, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 68846, .adv_w = 320, .box_w = 21, .box_h = 21, .ofs_x = -1, .ofs_y = -3}, + {.bitmap_index = 68986, .adv_w = 400, .box_w = 25, .box_h = 15, .ofs_x = 0, .ofs_y = 0}, + {.bitmap_index = 69106, .adv_w = 240, .box_w = 15, .box_h = 21, .ofs_x = 0, .ofs_y = -3}, + {.bitmap_index = 69165, .adv_w = 322, .box_w = 21, .box_h = 13, .ofs_x = 0, .ofs_y = 1} }; /*--------------------- @@ -12417,14 +12437,14 @@ static const uint16_t unicode_list_10[] = { }; static const uint16_t unicode_list_12[] = { - 0x0, 0xc, 0xceeb, 0xcef2, 0xcef5, 0xcef6, 0xcef7, 0xcefb, - 0xcefd, 0xceff, 0xcf03, 0xcf06, 0xcf0b, 0xcf10, 0xcf11, 0xcf12, - 0xcf28, 0xcf2d, 0xcf32, 0xcf35, 0xcf36, 0xcf37, 0xcf3b, 0xcf3c, - 0xcf3d, 0xcf3e, 0xcf51, 0xcf52, 0xcf58, 0xcf5a, 0xcf5b, 0xcf5e, - 0xcf61, 0xcf62, 0xcf63, 0xcf65, 0xcf7d, 0xcf7f, 0xcf96, 0xcfae, - 0xcfaf, 0xcfb1, 0xcfb3, 0xcfca, 0xcfd1, 0xcfd4, 0xcfdd, 0xd006, - 0xd00e, 0xd045, 0xd0d5, 0xd12a, 0xd12b, 0xd12c, 0xd12d, 0xd12e, - 0xd171, 0xd17d, 0xd1d7, 0xd1ee, 0xd444, 0xd6ac, 0xd78c + 0x0, 0xc, 0xceeb, 0xceef, 0xcef2, 0xcef5, 0xcef6, 0xcef7, + 0xcefb, 0xcefd, 0xceff, 0xcf03, 0xcf06, 0xcf0b, 0xcf10, 0xcf11, + 0xcf12, 0xcf28, 0xcf2d, 0xcf32, 0xcf35, 0xcf36, 0xcf37, 0xcf3b, + 0xcf3c, 0xcf3d, 0xcf3e, 0xcf51, 0xcf52, 0xcf58, 0xcf5a, 0xcf5b, + 0xcf5e, 0xcf61, 0xcf62, 0xcf63, 0xcf65, 0xcf7d, 0xcf7f, 0xcf96, + 0xcfae, 0xcfaf, 0xcfb1, 0xcfb3, 0xcfca, 0xcfd1, 0xcfd4, 0xcfdd, + 0xd006, 0xd00e, 0xd045, 0xd0d5, 0xd12a, 0xd12b, 0xd12c, 0xd12d, + 0xd12e, 0xd171, 0xd17d, 0xd1d7, 0xd1ee, 0xd444, 0xd6ac, 0xd78c }; /*Collect the unicode lists and glyph_id offsets*/ @@ -12480,7 +12500,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] = }, { .range_start = 8470, .range_length = 55181, .glyph_id_start = 1017, - .unicode_list = unicode_list_12, .glyph_id_ofs_list = NULL, .list_length = 63, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY + .unicode_list = unicode_list_12, .glyph_id_ofs_list = NULL, .list_length = 64, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY } }; @@ -12626,7 +12646,8 @@ static const uint8_t kern_left_class_mapping[] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0 }; /*Map glyph_ids to kern right classes*/ @@ -12766,7 +12787,8 @@ static const uint8_t kern_right_class_mapping[] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0 }; /*Kern values between classes*/ diff --git a/examples/companion_radio/ui-lvgl/fonts/ui_font_40.c b/examples/companion_radio/ui-lvgl/fonts/ui_font_40.c index 12529608..806c4562 100644 --- a/examples/companion_radio/ui-lvgl/fonts/ui_font_40.c +++ b/examples/companion_radio/ui-lvgl/fonts/ui_font_40.c @@ -1,7 +1,7 @@ /******************************************************************************* * Size: 40 px * Bpp: 4 - * Opts: --bpp 4 --size 40 --no-compress --font /private/tmp/claude-501/-Users-jakub-Documents-MeshCore-MeshCore/3d84fa48-e031-42a4-abec-01b33a2b06ef/scratchpad/fonts/NotoSans-SemiBold.ttf -r 0x20,0x2D,0x30-0x3A --format lvgl --lv-include lvgl.h --lv-font-name ui_font_40 -o /Users/jakub/Documents/MeshCore/MeshCore/examples/companion_radio/ui-lvgl/fonts/ui_font_40.c + * Opts: --bpp 4 --size 40 --no-compress --font /var/folders/1d/dgkmygls6x5gsb16pbn2mwlr0000gn/T//meshcore-ui-fonts/NotoSans-SemiBold.ttf -r 0x20,0x2D,0x30-0x3A --format lvgl --lv-include lvgl.h --lv-font-name ui_font_40 -o /Users/jakub/Documents/MeshCore/MeshCore/examples/companion_radio/ui-lvgl/fonts/ui_font_40.c ******************************************************************************/ #ifdef LV_LVGL_H_INCLUDE_SIMPLE diff --git a/examples/companion_radio/ui-new/NearbyScreen.h b/examples/companion_radio/ui-new/NearbyScreen.h index f256d733..96639f0f 100644 --- a/examples/companion_radio/ui-new/NearbyScreen.h +++ b/examples/companion_radio/ui-new/NearbyScreen.h @@ -10,18 +10,12 @@ // Filter (type) and sort are independent axes and combine freely. The action // menu (Hold Enter) is identical everywhere; only the per-row column and the // detail fields differ between sources. -class NearbyScreen : public UIScreen { +class NearbyScreen : public UIScreen, protected NearbyModel { UITask* _task; - // ── filter (type axis) ────────────────────────────────────────────────────── - enum Filter : uint8_t { F_ALL, F_FAV, F_COMP, F_RPT, F_ROOM, F_SNSR, F_COUNT }; - static const char* FILTER_LABELS[F_COUNT]; - - // ── sort axis ─────────────────────────────────────────────────────────────── - enum Sort : uint8_t { SORT_DIST, SORT_TIME }; - - // ── source ────────────────────────────────────────────────────────────────── - enum Source : uint8_t { SRC_STORED, SRC_SCAN }; + // List data, filter / sort / source and the refresh logic live in + // ui-core/NearbyModel.h (shared with ui-lvgl); this class adds selection, + // detail / navigate views, scan timing and the action menu. // ── action-menu actions (matched by id, not by row index) ──────────────────── enum Action : uint8_t { ACT_NAV, ACT_PING, ACT_WAYPOINT, ACT_LOCATOR, @@ -36,52 +30,11 @@ class NearbyScreen : public UIScreen { // startPickBotChannel()/startPickBotRoom() pick-mode idiom. bool _pick_admin_target = false; - // ── unified list entry ─────────────────────────────────────────────────────── - struct Entry { - char name[32]; - uint8_t type; - uint8_t pub_key[PUB_KEY_SIZE]; - // has_key: the full 32-byte pubkey is present -- what Ping and the base64 - // key view need. has_prefix: at least the leading FAVOURITE_PREFIX_LEN - // bytes are, which is all an identity-keyed reference needs (a Locator - // person target, resolved via UITask::resolvePersonPos). Every has_key row - // also has_prefix; the reverse doesn't hold -- a [LOC] share and a heard - // advert carry a prefix and nothing more. - bool has_key; - bool has_prefix; - // stored-source fields - int32_t lat_e6, lon_e6; - float dist_km; - uint32_t lastmod; - int contact_idx; - // scan-source fields - int8_t rssi, snr_x4, remote_snr_x4; - bool is_known; - // live-track ([LOC] share) overlay: this row's position/age came from a - // shared-location message. live_verified == true for a DM (pubkey) share, - // false for a channel (name, best-effort) share. - bool is_live; - bool live_verified; - // Favourite (ContactInfo::flags bit 0) -- always false for a scan/live row - // that isn't a contact, since only a contact can carry the flag. - bool fav; - }; - - static const int MAX_NEARBY = 32; - Entry _entries[MAX_NEARBY]; - int _count; int _sel; int _scroll; bool _detail; bool _nav = false; // full-screen navigate-to-node view (over detail) navview::EtaTracker _nav_eta; // closing-speed/ETA for the navigate view - int32_t _own_lat, _own_lon; - bool _own_gps; - - Source _source; - uint8_t _filter; - uint8_t _sort; - unsigned long _detail_refresh_ms; unsigned long _list_refresh_ms = 0; static const unsigned long DETAIL_REFRESH_MS = 10000UL; @@ -137,90 +90,15 @@ class NearbyScreen : public UIScreen { snprintf(buf, n, "%s ago", s); } - static const char* typeName(uint8_t t) { - switch (t) { - case ADV_TYPE_CHAT: return "Companion"; - case ADV_TYPE_REPEATER: return "Repeater"; - case ADV_TYPE_ROOM: return "Room"; - case ADV_TYPE_SENSOR: return "Sensor"; - default: return "Unknown"; - } - } - - static const char* typeShort(uint8_t t) { - switch (t) { - case ADV_TYPE_REPEATER: return "Rpt"; - case ADV_TYPE_SENSOR: return "Snsr"; - case ADV_TYPE_ROOM: return "Room"; - case ADV_TYPE_CHAT: return "Comp"; - default: return "?"; - } - } - // The selected entry, or nullptr when the list is empty. const Entry* selected() const { return (_count > 0 && _sel < _count) ? &_entries[_sel] : nullptr; } - bool typeMatchesFilter(uint8_t type, uint8_t flags, bool have_flags) const { - switch (_filter) { - case F_FAV: return have_flags && (flags & 0x01); - case F_COMP: return type == ADV_TYPE_CHAT; - case F_RPT: return type == ADV_TYPE_REPEATER; - case F_ROOM: return type == ADV_TYPE_ROOM; - case F_SNSR: return type == ADV_TYPE_SENSOR; - case F_ALL: - default: return true; - } - } - - // ── data refresh ────────────────────────────────────────────────────────────── - void refreshStored() { - _count = 0; - _own_lat = _own_lon = 0; - _own_gps = _task->currentLocation(_own_lat, _own_lon); - - int nc = the_mesh.getNumContacts(); - for (int i = 0; i < nc && _count < MAX_NEARBY; i++) { - ContactInfo ci; - // getContactByIdx() indexes the RAW contact table, whose first - // MAX_ANON_CONTACTS slots are reserved for anon-request bookkeeping - // (see BaseChatMesh::resetContacts()/ContactsIterator) -- getNumContacts() - // already excludes them from the count, so real contact 0 lives at raw - // index MAX_ANON_CONTACTS, not 0. Reading from 0 pulled those reserved - // (blank, type=ADV_TYPE_NONE) slots into the list as bogus "Unknown" - // rows, and silently dropped the same number of real contacts off the - // end -- while never touching the anon slots themselves, so it always - // reproduced the same way regardless of the auto-add overwrite setting. - if (!the_mesh.getContactByIdx(i + MAX_ANON_CONTACTS, ci)) continue; - if (!typeMatchesFilter(ci.type, ci.flags, true)) continue; - - Entry& e = _entries[_count++]; - strncpy(e.name, ci.name, sizeof(e.name) - 1); - e.name[sizeof(e.name) - 1] = '\0'; - memcpy(e.pub_key, ci.id.pub_key, PUB_KEY_SIZE); - e.has_key = true; - e.has_prefix = true; - e.lat_e6 = ci.gps_lat; - e.lon_e6 = ci.gps_lon; - bool remote_gps = (ci.gps_lat != 0 || ci.gps_lon != 0); - e.dist_km = (_own_gps && remote_gps) - ? geo::haversineKm(_own_lat, _own_lon, ci.gps_lat, ci.gps_lon) - : -1.0f; - e.type = ci.type; - e.fav = (ci.flags & 0x01) != 0; - e.contact_idx = i + MAX_ANON_CONTACTS; // raw index -- other lookups re-key off this directly - e.lastmod = ci.lastmod; - e.is_known = true; - e.is_live = false; - e.live_verified = false; - } - - mergeLiveTrack(); - mergeRecentlyHeard(); - sortStored(); - clampSelection(); - } + // ── data refresh (NearbyModel) + selection clamping ───────────────────────── + void refreshStored() { NearbyModel::refreshStored(); clampSelection(); } + void refreshScan() { NearbyModel::refreshScan(); clampSelection(); } + void refresh() { refreshModel(); clampSelection(); } // Rebuild the stored list (re-merge live shares + re-sort) while keeping the // currently highlighted node selected across the rebuild — by contact index @@ -236,173 +114,12 @@ class NearbyScreen : public UIScreen { saved_name[sizeof(saved_name) - 1] = '\0'; } refreshStored(); - if (saved_idx >= 0) { - for (int i = 0; i < _count; i++) - if (_entries[i].contact_idx == saved_idx) { _sel = i; return true; } - } else if (saved_live && saved_name[0]) { - for (int i = 0; i < _count; i++) - if (_entries[i].is_live && strncmp(_entries[i].name, saved_name, sizeof(saved_name) - 1) == 0) - { _sel = i; return true; } - } + int i = saved_idx >= 0 ? findContact(saved_idx) + : (saved_live && saved_name[0]) ? findLiveByName(saved_name) : -1; + if (i >= 0) { _sel = i; return true; } return false; } - // Overlay live [LOC] shares onto the stored list: refresh a matching contact - // with the fresher shared position, and append senders who aren't contacts so - // a group sharing on a channel still shows up. DM shares match by pubkey - // prefix (verified); channel shares match by name (best-effort). - void mergeLiveTrack() { - if (!_task) return; - LiveTrackStore& lt = _task->liveTrack(); - uint32_t now = rtc_clock.getCurrentTime(); - for (int i = 0; i < LiveTrackStore::CAPACITY; i++) { - if (!lt.isActive(i, now)) continue; - const LiveTrackStore::Entry& s = lt.slotAt(i); - - int m = -1; - for (int j = 0; j < _count; j++) { - if (s.verified && _entries[j].has_key - && memcmp(_entries[j].pub_key, s.key, LiveTrackStore::KEY_LEN) == 0) { m = j; break; } - if (!s.verified && strncmp(_entries[j].name, s.name, sizeof(_entries[j].name) - 1) == 0) { m = j; break; } - } - - if (m >= 0) { - Entry& e = _entries[m]; - // A [LOC] share is an explicit "here I am now", so it defines the pin and - // the distance (used for proximity sorting). Recency for the time sort is - // the most recent of the advert and the share. - e.lat_e6 = s.lat_1e6; - e.lon_e6 = s.lon_1e6; - e.dist_km = _own_gps ? geo::haversineKm(_own_lat, _own_lon, s.lat_1e6, s.lon_1e6) : -1.0f; - if (s.ts > e.lastmod) e.lastmod = s.ts; - e.is_live = true; - e.live_verified = s.verified; - } else if (_count < MAX_NEARBY && typeMatchesFilter(ADV_TYPE_CHAT, 0, false)) { - // A sender we don't have as a contact: treat it as a companion (the only - // node type that shares position), so the type filter still applies — - // e.g. it must not appear under the Repeater/Room/Sensor filters. - Entry& e = _entries[_count++]; - memset(&e, 0, sizeof(e)); - strncpy(e.name, s.name, sizeof(e.name) - 1); - e.name[sizeof(e.name) - 1] = '\0'; - // We only keep a key *prefix* for shares, not the full pubkey, so Ping - // and the base64 key view (which need 32 bytes) stay unavailable for a - // non-contact live entry. Navigate / Save-waypoint work off lat/lon. - // A DM share is pubkey-keyed, so the prefix is real and good enough to - // keep re-resolving them as a Locator person target; a channel share is - // matched by name and carries no identity at all. - e.has_key = false; - e.has_prefix = s.verified; - if (s.verified) memcpy(e.pub_key, s.key, LiveTrackStore::KEY_LEN); - e.type = ADV_TYPE_CHAT; - e.lat_e6 = s.lat_1e6; - e.lon_e6 = s.lon_1e6; - e.dist_km = _own_gps ? geo::haversineKm(_own_lat, _own_lon, s.lat_1e6, s.lon_1e6) : -1.0f; - e.lastmod = s.ts; - e.contact_idx = -1; - e.is_known = false; - e.is_live = true; - e.live_verified = s.verified; - } - } - } - - // Fold passively-heard adverts that aren't already listed (contact or live) into - // the list as name+age rows — no full key/GPS, so informational only. This is - // what absorbs the old standalone "Recent adverts" home page. Gated to the All - // filter because AdvertPath carries no node type to filter or sort-by-dist on. - void mergeRecentlyHeard() { - if (_filter != F_ALL || !_task) return; - static AdvertPath heard[8]; // scratch — refreshStored isn't reentrant - int n = the_mesh.getRecentlyHeard(heard, 8); - for (int i = 0; i < n && _count < MAX_NEARBY; i++) { - AdvertPath& a = heard[i]; - if (a.name[0] == 0) continue; - bool dup = false; - for (int j = 0; j < _count; j++) { - if (_entries[j].has_key && - memcmp(_entries[j].pub_key, a.pubkey_prefix, sizeof(a.pubkey_prefix)) == 0) { dup = true; break; } - if (strncmp(_entries[j].name, a.name, sizeof(a.name) - 1) == 0) { dup = true; break; } - } - if (dup) continue; - Entry& e = _entries[_count++]; - memset(&e, 0, sizeof(e)); - strncpy(e.name, a.name, sizeof(e.name) - 1); - e.name[sizeof(e.name) - 1] = '\0'; - e.type = ADV_TYPE_CHAT; // unknown from AdvertPath — best-effort label - e.has_key = false; - e.has_prefix = true; - memcpy(e.pub_key, a.pubkey_prefix, sizeof(a.pubkey_prefix)); - e.dist_km = -1.0f; - e.lastmod = a.recv_timestamp; - e.contact_idx = -1; - e.is_known = false; - e.is_live = false; - } - } - - void sortStored() { - uint32_t now_ts = rtc_clock.getCurrentTime(); - NodePrefs* p = _task->getNodePrefs(); - const bool fav_first = !(p && p->fav_sort_off); - for (int i = 0; i < _count - 1; i++) { - int best = i; - for (int j = i + 1; j < _count; j++) { - if (fav_first && _entries[j].fav != _entries[best].fav) { - if (_entries[j].fav) best = j; // favourites outrank the time/distance key - continue; - } - if (_sort == SORT_TIME) { - // lastmod=0 or lastmod>now (RTC not synced) → "unknown" → sort to bottom. - uint32_t tj = (_entries[j].lastmod > 0 && now_ts >= _entries[j].lastmod) ? _entries[j].lastmod : 0; - uint32_t tb = (_entries[best].lastmod > 0 && now_ts >= _entries[best].lastmod) ? _entries[best].lastmod : 0; - if (tj > 0 && (tb == 0 || tj > tb)) best = j; // descending — most recent first - } else { - float dj = _entries[j].dist_km, db = _entries[best].dist_km; - if (dj >= 0.0f && (db < 0.0f || dj < db)) best = j; // ascending — closest first - } - } - if (best != i) { Entry tmp = _entries[i]; _entries[i] = _entries[best]; _entries[best] = tmp; } - } - } - - void refreshScan() { - static DiscoverResult dr[DISCOVER_RESULTS_MAX]; // scratch — refresh isn't reentrant - int n = the_mesh.getDiscoverResults(dr, DISCOVER_RESULTS_MAX); - _count = 0; - for (int i = 0; i < n && _count < MAX_NEARBY; i++) { - if (!typeMatchesFilter(dr[i].type, 0, false)) continue; - Entry& e = _entries[_count++]; - strncpy(e.name, dr[i].name, sizeof(e.name) - 1); - e.name[sizeof(e.name) - 1] = '\0'; - e.type = dr[i].type; - memcpy(e.pub_key, dr[i].pub_key, PUB_KEY_SIZE); - e.has_key = true; - e.has_prefix = true; - e.rssi = dr[i].rssi; - e.snr_x4 = dr[i].snr_x4; - e.remote_snr_x4 = dr[i].remote_snr_x4; - e.is_known = dr[i].is_known; - e.fav = false; - e.lat_e6 = e.lon_e6 = 0; - e.dist_km = -1.0f; - e.lastmod = 0; - e.contact_idx = -1; - e.is_live = false; - e.live_verified = false; - } - // strongest first - for (int i = 0; i < _count - 1; i++) { - int best = i; - for (int j = i + 1; j < _count; j++) - if (_entries[j].rssi > _entries[best].rssi) best = j; - if (best != i) { Entry tmp = _entries[i]; _entries[i] = _entries[best]; _entries[best] = tmp; } - } - clampSelection(); - } - - void refresh() { if (_source == SRC_SCAN) refreshScan(); else refreshStored(); } - void clampSelection() { if (_count == 0) { _sel = _scroll = 0; } else if (_sel >= _count) { _sel = _count - 1; if (_scroll > _sel) _scroll = _sel; } @@ -804,17 +521,16 @@ class NearbyScreen : public UIScreen { // side and wrap around (the tab before "All" is "Snsr", and vice-versa), // matching the wrap-around LEFT/RIGHT cycle. void drawFilterTabs(DisplayDriver& display) { - tabbar::draw(display, FILTER_LABELS, F_COUNT, _filter, display.menuHintWidth()); + tabbar::draw(display, filterLabels(), F_COUNT, _filter, display.menuHintWidth()); display.drawContextMenuHint(DisplayDriver::LIGHT, ctxMenuOpen()); // Nodes list has a Hold-Enter menu } public: NearbyScreen(UITask* task) - : _task(task), _count(0), _sel(0), _scroll(0), _detail(false), - _own_lat(0), _own_lon(0), _own_gps(false), - _source(SRC_STORED), _filter(F_ALL), _sort(SORT_DIST), + : _task(task), _sel(0), _scroll(0), _detail(false), _detail_refresh_ms(0), _scanning(false), _scan_started_ms(0), _menu_action_count(0), _pinging(false), _ping_started_ms(0) { + bindModel(&task->core(), task->getNodePrefs()); resetPingLines(); _sort_label[0] = '\0'; } @@ -895,7 +611,7 @@ public: int dist_col = display.width() - display.getCharWidth() * 7; display.setColor(DisplayDriver::LIGHT); - const char* flt = (_filter != F_ALL) ? FILTER_LABELS[_filter] : nullptr; + const char* flt = (_filter != F_ALL) ? filterLabel(_filter) : nullptr; if (_source == SRC_SCAN) { char title[28]; const char* base = _scanning ? "SCANNING" : "SCAN"; @@ -1056,4 +772,3 @@ public: } }; -const char* NearbyScreen::FILTER_LABELS[F_COUNT] = { "All", "Fav", "Comp", "Rpt", "Room", "Snsr" }; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 8e9b3e27..f21e62e2 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -167,6 +167,7 @@ static const int QUICK_MSGS_MAX = 10; #include "RingtoneEditorScreen.h" #include "BotScreen.h" #include "AdminScreen.h" +#include "../ui-core/NearbyModel.h" #include "NearbyScreen.h" #include "DashboardConfigScreen.h" #include "AutoAdvertScreen.h" diff --git a/variants/sim/web/lvgl.html b/variants/sim/web/lvgl.html index c2317440..eb6f575d 100644 --- a/variants/sim/web/lvgl.html +++ b/variants/sim/web/lvgl.html @@ -1,11 +1,13 @@