Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
Binary file added .github/.DS_Store
Binary file not shown.
Binary file added .trunk/.DS_Store
Binary file not shown.
Binary file added generated/.DS_Store
Binary file not shown.
Binary file added include/.DS_Store
Binary file not shown.
3 changes: 3 additions & 0 deletions include/graphics/view/TFT/TFTView_320x240.h
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ class TFTView_320x240 : public MeshtasticView
static void ui_event_mapNodeButton(lv_event_t *e);
static void ui_event_chatNodeButton(lv_event_t *e);
static void ui_event_positionButton(lv_event_t *e);
static void ui_event_nodesPanelScroll(lv_event_t *e);

// animations
static void ui_anim_node_panel_cb(void *var, int32_t v);
Expand All @@ -399,6 +400,8 @@ class TFTView_320x240 : public MeshtasticView
uint32_t nodesFiltered; // no. hidden nodes in node list
bool nodesChanged; // true if nodes changed (added or purged)
bool processingFilter; // indicates that filtering is ongoing
uint16_t nodesScrollDisplayLimit; // current node display limit for infinite scroll
bool nodesScrollLoadingMore; // prevent loading multiple batches simultaneously
bool packetLogEnabled; // display received packets
bool detectorRunning; // meshDetector is active
bool cardDetected; // SD has been detected
Expand Down
Binary file added source/.DS_Store
Binary file not shown.
154 changes: 146 additions & 8 deletions source/graphics/TFT/TFTView_320x240.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ TFTView_320x240 *TFTView_320x240::instance(const DisplayDriverConfig &cfg)

TFTView_320x240::TFTView_320x240(const DisplayDriverConfig *cfg, DisplayDriver *driver)
: MeshtasticView(cfg, driver, new ViewController), screensInitialised(false), nodesFiltered(0), nodesChanged(true),
processingFilter(false), packetLogEnabled(false), detectorRunning(false), cardDetected(false), formatSD(false),
packetCounter(0), actTime(0), uptime(0), lastHeard(0), hasPosition(false), myLatitude(0), myLongitude(0),
processingFilter(false), nodesScrollDisplayLimit(15), packetLogEnabled(false), detectorRunning(false), cardDetected(false),
formatSD(false), packetCounter(0), actTime(0), uptime(0), lastHeard(0), hasPosition(false), myLatitude(0), myLongitude(0),
topNodeLL(nullptr), scans(0), selectedHops(0), chooseNodeSignalScanner(false), chooseNodeTraceRoute(false), qr(nullptr),
db{}
{
Expand Down Expand Up @@ -169,7 +169,8 @@ void TFTView_320x240::init(IClientBase *client)

ui_init_boot();
FileLoader::init(&fileSystem);
FileLoader::loadBootImage(objects.boot_logo);
if (!FileLoader::loadBootImage(objects.boot_logo))
lv_image_set_src(objects.boot_logo, &img_meshtastic_boot_logo_image);
// if boot logo is too big remove the label and center the image
lv_obj_update_layout(objects.boot_logo);
if (lv_obj_get_height(objects.boot_logo) > lv_display_get_vertical_resolution(displaydriver->getDisplay()) / 2) {
Expand Down Expand Up @@ -728,6 +729,9 @@ void TFTView_320x240::ui_events_init(void)
// node and channel buttons
lv_obj_add_event_cb(objects.node_button, ui_event_NodeButton, LV_EVENT_ALL, (void *)ownNode);

// nodes panel - infinite scroll support
lv_obj_add_event_cb(objects.nodes_panel, this->ui_event_nodesPanelScroll, LV_EVENT_SCROLL, NULL);

// 8 channel buttons
lv_obj_add_event_cb(objects.channel_button0, ui_event_ChannelButton, LV_EVENT_ALL, (void *)0);
lv_obj_add_event_cb(objects.channel_button1, ui_event_ChannelButton, LV_EVENT_ALL, (void *)1);
Expand Down Expand Up @@ -918,7 +922,7 @@ void TFTView_320x240::timer_event_shutdown(lv_timer_t *timer)
THIS->controller->stop();
delay(1000);
#if defined(ARCH_PORTDUINO)
exit(0);
exit(2);
#elif defined(ARCH_ESP32)
esp_deep_sleep_start();
#else
Expand Down Expand Up @@ -2378,6 +2382,116 @@ void TFTView_320x240::ui_event_positionButton(lv_event_t *e)
}
}

/**
* @brief Hard object culling for nodes panel
* Actually DELETE nodes outside viewport buffer, recreate when scrolling back
* Dramatically reduces LVGL object count and layout cost
* Trade-off: slight delay when recreating nodes (but faster overall)
*/
void TFTView_320x240::ui_event_nodesPanelScroll(lv_event_t *e)
{
auto *self = static_cast<TFTView_320x240 *>(lv_event_get_user_data(e));
if (!self)
return;

lv_event_code_t event_code = lv_event_get_code(e);

// Only do expensive updates on scroll END, not during continuous scroll
if (event_code == LV_EVENT_SCROLL_END) {
// Load more nodes if near bottom
lv_obj_t *panel = (lv_obj_t *)lv_event_get_target(e);
const lv_coord_t LOAD_DISTANCE = 400;

if (lv_obj_get_scroll_bottom(panel) < LOAD_DISTANCE && self->nodesScrollDisplayLimit < MAX_NUM_NODES_VIEW &&
!self->nodesScrollLoadingMore) {
self->nodesScrollLoadingMore = true;

uint16_t new_limit = self->nodesScrollDisplayLimit + 10;
if (new_limit > MAX_NUM_NODES_VIEW) {
new_limit = MAX_NUM_NODES_VIEW;
}

self->nodesScrollDisplayLimit = new_limit;
lv_obj_update_layout(panel);

ILOG_DEBUG("Loaded nodes batch, now displaying %d nodes", self->nodesScrollDisplayLimit);

self->nodesScrollLoadingMore = false;
}

return;
}

// During continuous scroll: only hide/show visible nodes (no text/style updates)
if (event_code != LV_EVENT_SCROLL) {
return;
}

lv_obj_t *panel = (lv_obj_t *)lv_event_get_target(e);

// Prevent re-entry during hide/show updates
static bool scroll_running = false;
if (scroll_running)
return;
scroll_running = true;

// Hide nodes with 0-node buffer strategy - SINGLE PASS
lv_coord_t scroll_y = lv_obj_get_scroll_y(panel);
lv_coord_t panel_h = lv_obj_get_height(panel);

// Find first visible node AND hide/show in single loop
int node_idx = 0;

for (auto &node_pair : self->nodes) {
lv_obj_t *node_obj = node_pair.second;
if (!node_obj)
continue;

// Only process up to display limit for performance
if (node_idx >= self->nodesScrollDisplayLimit)
break;

lv_coord_t node_y = lv_obj_get_y(node_obj);
lv_coord_t node_h = lv_obj_get_height(node_obj);

// Check if node is within viewport
bool is_visible = (node_y + node_h >= scroll_y && node_y <= scroll_y + panel_h);

if (is_visible) {
// Show this node
if (lv_obj_has_flag(node_obj, LV_OBJ_FLAG_HIDDEN)) {
lv_obj_clear_flag(node_obj, LV_OBJ_FLAG_HIDDEN);
}
} else {
// Hide this node
if (!lv_obj_has_flag(node_obj, LV_OBJ_FLAG_HIDDEN)) {
lv_obj_add_flag(node_obj, LV_OBJ_FLAG_HIDDEN);
}
}

node_idx++;
}

// Hide all nodes beyond display limit
for (auto &node_pair : self->nodes) {
lv_obj_t *node_obj = node_pair.second;
if (!node_obj)
continue;

if (node_idx >= MAX_NUM_NODES_VIEW)
break;

if (node_idx >= self->nodesScrollDisplayLimit) {
if (!lv_obj_has_flag(node_obj, LV_OBJ_FLAG_HIDDEN)) {
lv_obj_add_flag(node_obj, LV_OBJ_FLAG_HIDDEN);
}
}
node_idx++;
}

scroll_running = false;
}

void TFTView_320x240::ui_screen_event_cb(lv_event_t *e)
{
if (THIS->activePanel == objects.map_panel) {
Expand Down Expand Up @@ -4685,6 +4799,15 @@ void TFTView_320x240::addNode(uint32_t nodeNum, uint8_t ch, const char *userShor
lv_obj_set_style_align(ui_Telemetry2Label, LV_ALIGN_TOP_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_text_align(ui_Telemetry2Label, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN | LV_STATE_DEFAULT);

// optimisation: hide all 6ix extended labels by default; enable only when set
// lv_obj_add_flag(ui_lastHeardLabel, LV_OBJ_FLAG_HIDDEN); // lastHeard
lv_obj_add_flag(ui_BatteryLabel, LV_OBJ_FLAG_HIDDEN); // Autohide battery
lv_obj_add_flag(ui_SignalLabel, LV_OBJ_FLAG_HIDDEN); // Autohide signal/hops
lv_obj_add_flag(ui_PositionLabel, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ui_Position2Label, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ui_Telemetry1Label, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(ui_Telemetry2Label, LV_OBJ_FLAG_HIDDEN);

lv_obj_add_event_cb(nodeButton, ui_event_NodeButton, LV_EVENT_ALL, (void *)nodeNum);

// move node into new position within nodePanel
Expand Down Expand Up @@ -4882,6 +5005,8 @@ void TFTView_320x240::updatePosition(uint32_t nodeNum, int32_t lat, int32_t lon,
// store lat/lon in user_data, because we need these values later to calculate the distance to us
panel->LV_OBJ_IDX(node_pos1_idx)->user_data = (void *)lat;
panel->LV_OBJ_IDX(node_pos2_idx)->user_data = (void *)lon;
lv_obj_remove_flag(panel->LV_OBJ_IDX(node_pos1_idx), LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(panel->LV_OBJ_IDX(node_pos2_idx), LV_OBJ_FLAG_HIDDEN);
}

applyNodesFilter(nodeNum);
Expand Down Expand Up @@ -4991,6 +5116,7 @@ void TFTView_320x240::updateMetrics(uint32_t nodeNum, uint32_t bat_level, float
bat_level = std::min(bat_level, (uint32_t)100);
sprintf(buf, "%d%% %0.2fV", bat_level, voltage);
lv_label_set_text(it->second->LV_OBJ_IDX(node_bat_idx), buf);
lv_obj_remove_flag(it->second->LV_OBJ_IDX(node_bat_idx), LV_OBJ_FLAG_HIDDEN);
}
}
}
Expand All @@ -5016,11 +5142,13 @@ void TFTView_320x240::updateEnvironmentMetrics(uint32_t nodeNum, const meshtasti
}
}
lv_label_set_text(it->second->LV_OBJ_IDX(node_tm1_idx), buf);
lv_obj_remove_flag(it->second->LV_OBJ_IDX(node_tm1_idx), LV_OBJ_FLAG_HIDDEN);

if (metrics.iaq > 0 && metrics.iaq < 1000) {
sprintf(buf, "IAQ: %d %.1fV %.1fmA", metrics.iaq, metrics.voltage, metrics.current);
lv_label_set_text(it->second->LV_OBJ_IDX(node_tm2_idx), buf);
it->second->LV_OBJ_IDX(node_tm2_idx)->user_data = (void *)(uint32_t)metrics.iaq;
lv_obj_remove_flag(it->second->LV_OBJ_IDX(node_tm2_idx), LV_OBJ_FLAG_HIDDEN);
}
applyNodesFilter(nodeNum);
}
Expand Down Expand Up @@ -5064,6 +5192,7 @@ void TFTView_320x240::updateSignalStrength(uint32_t nodeNum, int32_t rssi, float
}
lv_label_set_text(it->second->LV_OBJ_IDX(node_sig_idx), buf);
it->second->LV_OBJ_IDX(node_sig_idx)->user_data = 0;
lv_obj_remove_flag(it->second->LV_OBJ_IDX(node_sig_idx), LV_OBJ_FLAG_HIDDEN);
}
}
}
Expand All @@ -5077,6 +5206,7 @@ void TFTView_320x240::updateHopsAway(uint32_t nodeNum, uint8_t hopsAway)
sprintf(buf, _("hops: %d"), (int)hopsAway);
lv_label_set_text(it->second->LV_OBJ_IDX(node_sig_idx), buf);
it->second->LV_OBJ_IDX(node_sig_idx)->user_data = (void *)(unsigned long)hopsAway;
lv_obj_remove_flag(it->second->LV_OBJ_IDX(node_sig_idx), LV_OBJ_FLAG_HIDDEN);
}
}
}
Expand Down Expand Up @@ -6546,6 +6676,7 @@ void TFTView_320x240::addChat(uint32_t from, uint32_t to, uint8_t ch)
lv_obj_set_style_pad_bottom(chatBtn, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_row(chatBtn, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_column(chatBtn, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_color(chatBtn, lv_color_hex(0xff141723), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_move_to_index(chatBtn, 0);

char buf[64];
Expand Down Expand Up @@ -6899,7 +7030,6 @@ void TFTView_320x240::setGroupFocus(lv_obj_t *panel)
lv_group_focus_obj(panel->spec_attr->children[1]); // TODO: does not work
}
} else if (panel == objects.map_panel) {

} else if (panel == objects.settings_screen_lock_panel) {
lv_group_focus_obj(objects.screen_lock_button_matrix);
} else if (panel == objects.controller_panel) {
Expand Down Expand Up @@ -7020,19 +7150,27 @@ void TFTView_320x240::updateNodesStatus(void)
void TFTView_320x240::updateNodesFiltered(bool reset)
{
static auto it = nodes.begin();
static uint16_t processedCount = 0; // Track how many nodes we've processed in current session

if (reset || nodesChanged) {
nodesFiltered = 0;
nodesChanged = false;
processingFilter = true;
it = nodes.begin();
processedCount = 0;
}

for (int i = 0; i < 10 && it != nodes.end(); i++) {
// Process nodes in batches of 10, but respect the display limit for infinite scroll
uint16_t batchSize = 10;
uint16_t processLimit = nodesScrollDisplayLimit; // Limit based on infinite scroll display count

for (int i = 0; i < batchSize && it != nodes.end() && processedCount < processLimit; i++) {
applyNodesFilter(it->first, true);
it++;
processedCount++;
}

if (it == nodes.end()) {
if (it == nodes.end() || processedCount >= processLimit) {
processingFilter = false;
}
updateNodesStatus();
Expand Down Expand Up @@ -7366,4 +7504,4 @@ void action_on_boot_screen_displayed(lv_event_t *e)
}
}

#endif
#endif
Loading