touch: add Direct (0-hop) filter to contact list and map

Contact list gains a "Direct (0-hop)" option in the Filter sheet (value 5)
that shows only nodes heard without any repeater hops (out_path_len == 0).
Header chip shows "Dir" when active. Selecting a filter now auto-dismisses
the sheet (matching sort behaviour).

Map options panel gets a "Direct (0-hop) only" toggle that hides both
contact markers and link lines for any multi-hop or path-unknown node.
This commit is contained in:
Ruben Laban committed 2026-06-23 14:36:57 +02:00
1 parent f985458363
commit 29204dd33f
1 file changed
+29 -17
+29 -17
View File
@@ -14861,12 +14861,12 @@ static lv_obj_t* s_ct_normal_bar = nullptr; // header toolbar shown in normal m
static lv_obj_t* s_ct_select_bar = nullptr; // header toolbar shown in select mode
static lv_obj_t* s_ct_del_btn = nullptr; // "Delete (N)" button in the select bar
static lv_obj_t* s_ct_sort_btns[3] = { nullptr, nullptr, nullptr };
static lv_obj_t* s_ct_filter_btns[5] = { nullptr, nullptr, nullptr, nullptr, nullptr };
static lv_obj_t* s_ct_filter_btns[6] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
static lv_obj_t* s_ct_filter_btn_lbl = nullptr; // header Filter button label (shows the active filter)
static lv_obj_t* s_ct_disc_badge = nullptr; // count badge on the header Discovered button
static const char* contactsFilterShortLabel() {
switch (g_lv.contacts_filter) {
case 1u: return "RPT"; case 2u: return "Peer"; case 3u: return "Fav"; case 4u: return "Loc";
case 1u: return "RPT"; case 2u: return "Peer"; case 3u: return "Fav"; case 4u: return "Loc"; case 5u: return "Dir";
default: return "All";
}
}
@@ -14899,6 +14899,7 @@ static bool ctPassesFilter(const ContactInfo& c, bool is_fav, int fav_count, con
case 2u: if(is_rep) return false; break; // peers
case 3u: if(fav_count>0 && !is_fav) return false; break; // favorites
case 4u: if(!(c.gps_lat!=0 || c.gps_lon!=0)) return false; break; // has location
case 5u: if(c.out_path_len != 0) return false; break; // 0-hop / direct neighbors only
default: break; // 0 = all
}
if(needle && needle[0]){
@@ -15100,7 +15101,7 @@ static void ctPaintOpt(lv_obj_t* b, bool active){
}
static void ctSheetRepaint(){
for(int i=0;i<3;++i) ctPaintOpt(s_ct_sort_btns[i], g_contacts_sort==(uint8_t)i);
for(int i=0;i<5;++i) ctPaintOpt(s_ct_filter_btns[i], g_lv.contacts_filter==(uint8_t)i);
for(int i=0;i<6;++i) ctPaintOpt(s_ct_filter_btns[i], g_lv.contacts_filter==(uint8_t)i);
}
static void ctSortOptCb(lv_event_t* e){
if(lv_event_get_code(e)!=LV_EVENT_CLICKED) return;
@@ -15113,7 +15114,8 @@ static void ctFilterOptCb(lv_event_t* e){
g_lv.contacts_filter = (uint8_t)(uintptr_t)lv_event_get_user_data(e);
updateContactsFilterSegments();
if(s_ct_filter_btn_lbl) lv_label_set_text_fmt(s_ct_filter_btn_lbl, "%s %s", LV_SYMBOL_DOWN, contactsFilterShortLabel());
ctSheetRepaint(); s_ct_list_force = true; refreshContactsList();
s_ct_list_force = true; refreshContactsList();
ctSortSheetClose(); // apply and dismiss, like sort does
}
static void ctSelectFromSheetCb(lv_event_t* e){
if(lv_event_get_code(e)!=LV_EVENT_CLICKED) return;
@@ -15175,7 +15177,7 @@ static lv_obj_t* ctOpenOptionSheet(const char* title){
return card;
}
static void openContactsSortSheet(){
for(int i=0;i<5;++i) s_ct_filter_btns[i]=nullptr; // the filter sheet's buttons are gone now
for(int i=0;i<6;++i) s_ct_filter_btns[i]=nullptr; // the filter sheet's buttons are gone now
lv_obj_t* card = ctOpenOptionSheet("Sort by");
s_ct_sort_btns[0] = ctSheetOption(card, "Name (A-Z)", ctSortOptCb, CONTACTS_SORT_AZ, g_contacts_sort==CONTACTS_SORT_AZ);
s_ct_sort_btns[1] = ctSheetOption(card, "Recently heard", ctSortOptCb, CONTACTS_SORT_LAST_HEARD, g_contacts_sort==CONTACTS_SORT_LAST_HEARD);
@@ -15187,11 +15189,12 @@ static void openContactsSortSheetCb(lv_event_t* e){ if(lv_event_get_code(e)==LV_
static void openContactsFilterSheet(){
for(int i=0;i<3;++i) s_ct_sort_btns[i]=nullptr; // the sort sheet's buttons are gone now
lv_obj_t* card = ctOpenOptionSheet("Filter");
s_ct_filter_btns[0] = ctSheetOption(card, "All", ctFilterOptCb, 0, g_lv.contacts_filter==0);
s_ct_filter_btns[1] = ctSheetOption(card, "Repeaters", ctFilterOptCb, 1, g_lv.contacts_filter==1);
s_ct_filter_btns[2] = ctSheetOption(card, "Peers", ctFilterOptCb, 2, g_lv.contacts_filter==2);
s_ct_filter_btns[3] = ctSheetOption(card, "Favorites", ctFilterOptCb, 3, g_lv.contacts_filter==3);
s_ct_filter_btns[4] = ctSheetOption(card, "Has location", ctFilterOptCb, 4, g_lv.contacts_filter==4);
s_ct_filter_btns[0] = ctSheetOption(card, "All", ctFilterOptCb, 0, g_lv.contacts_filter==0);
s_ct_filter_btns[1] = ctSheetOption(card, "Repeaters", ctFilterOptCb, 1, g_lv.contacts_filter==1);
s_ct_filter_btns[2] = ctSheetOption(card, "Peers", ctFilterOptCb, 2, g_lv.contacts_filter==2);
s_ct_filter_btns[3] = ctSheetOption(card, "Favorites", ctFilterOptCb, 3, g_lv.contacts_filter==3);
s_ct_filter_btns[4] = ctSheetOption(card, "Has location", ctFilterOptCb, 4, g_lv.contacts_filter==4);
s_ct_filter_btns[5] = ctSheetOption(card, "Direct (0-hop)", ctFilterOptCb, 5, g_lv.contacts_filter==5);
}
static void openContactsFilterSheetCb(lv_event_t* e){ if(lv_event_get_code(e)==LV_EVENT_CLICKED) openContactsFilterSheet(); }
@@ -16854,9 +16857,10 @@ static lv_point_t s_map_link_pts[k_map_markers_max][2];
// Per-element visibility of the map's on-screen text/markers (persisted; all
// default shown). Coords = bottom-left read-out, TileXYZ = the zoom + tile path
// line, Contacts = the contact markers.
static bool s_map_show_coords = true;
static bool s_map_show_tilexyz = true;
static bool s_map_show_contacts = true;
static bool s_map_show_coords = true;
static bool s_map_show_tilexyz = true;
static bool s_map_show_contacts = true;
static bool s_map_direct_only = false; // when true, only 0-hop (directly-heard) contacts appear
// Apply the coords/tile-line visibility flags to the two corner labels. Safe to
// call before the labels exist (null-guarded); invoked at map build + on toggle.
@@ -16962,6 +16966,7 @@ static void renderMapMarkers() {
ContactInfo c;
if (!the_mesh.getContactByIdx(i, c)) continue;
if (c.gps_lat == 0 && c.gps_lon == 0) continue;
if (s_map_direct_only && c.out_path_len != 0) continue;
double mwx, mwy;
latLonToWorldPx((double)c.gps_lat / 1.0e6, (double)c.gps_lon / 1.0e6,
s_map_zoom, &mwx, &mwy);
@@ -17009,6 +17014,7 @@ static void renderMapMarkers() {
ContactInfo c;
if (!the_mesh.getContactByIdx(i, c)) continue;
if (c.gps_lat == 0 && c.gps_lon == 0) continue;
if (s_map_direct_only && c.out_path_len != 0) continue;
const double lat = (double)c.gps_lat / 1.0e6;
const double lon = (double)c.gps_lon / 1.0e6;
@@ -17437,6 +17443,11 @@ static void mapOptContactsCb(lv_event_t* e) {
#endif
renderMapMarkers(); // add/remove the contact markers (+ their links)
}
static void mapOptDirectOnlyCb(lv_event_t* e) {
if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
s_map_direct_only = lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED);
renderMapMarkers();
}
static void mapZoomControlsApply(); // fwd (defined near the map build) — reposition zoom controls
// Map zoom-control style toggle (map options popup): ON = +/- buttons, OFF = slider.
@@ -17692,10 +17703,11 @@ static void openMapOptions() {
// Rows: per-element on-map visibility (link lines / coords / tile z-x-y / contacts).
{
struct { const char* label; bool state; lv_event_cb_t cb; } rows[] = {
{ "Show link lines", s_map_show_links, mapOptLinesCb },
{ "Show coordinates", s_map_show_coords, mapOptCoordsCb },
{ "Show tile z/x/y", s_map_show_tilexyz, mapOptTileXYZCb },
{ "Show contacts", s_map_show_contacts, mapOptContactsCb },
{ "Show link lines", s_map_show_links, mapOptLinesCb },
{ "Show coordinates", s_map_show_coords, mapOptCoordsCb },
{ "Show tile z/x/y", s_map_show_tilexyz, mapOptTileXYZCb },
{ "Show contacts", s_map_show_contacts, mapOptContactsCb },
{ "Direct (0-hop) only", s_map_direct_only, mapOptDirectOnlyCb },
};
for (auto& r : rows) {
lv_obj_t* l = lv_label_create(card);