mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-15 17:42:12 +00:00
Node list cleanup and optimization
This commit is contained in:
parent
5fa236c77d
commit
35d4784c5c
@ -1883,16 +1883,41 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void (*EntryRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int);
|
// Combined dynamic node list frame cycling through LastHeard, HopSignal, and Distance modes
|
||||||
typedef void (*NodeExtrasRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int columnWidth, float heading,
|
// Uses a single frame and changes data every few seconds (E-Ink variant is separate)
|
||||||
double lat, double lon);
|
|
||||||
|
// =============================
|
||||||
|
// Shared Types and Structures
|
||||||
|
// =============================
|
||||||
|
typedef void (*EntryRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int);
|
||||||
|
typedef void (*NodeExtrasRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int, float, double, double);
|
||||||
|
|
||||||
|
|
||||||
// h! Each node entry holds a reference to its info and how long ago it was heard from
|
|
||||||
struct NodeEntry {
|
struct NodeEntry {
|
||||||
meshtastic_NodeInfoLite *node;
|
meshtastic_NodeInfoLite *node;
|
||||||
uint32_t lastHeard;
|
uint32_t lastHeard;
|
||||||
|
float cachedDistance = -1.0f; // Only used in distance mode
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// Shared Enums and Timing Logic
|
||||||
|
// =============================
|
||||||
|
enum NodeListMode {
|
||||||
|
MODE_LAST_HEARD = 0,
|
||||||
|
MODE_HOP_SIGNAL = 1,
|
||||||
|
MODE_DISTANCE = 2,
|
||||||
|
MODE_COUNT = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
static NodeListMode currentMode = MODE_LAST_HEARD;
|
||||||
|
static unsigned long lastModeSwitchTime = 0;
|
||||||
|
static int scrollIndex = 0;
|
||||||
|
|
||||||
|
// Use dynamic timing based on mode
|
||||||
|
unsigned long getModeCycleIntervalMs() {
|
||||||
|
return (currentMode == MODE_DISTANCE) ? 4000 : 2000;
|
||||||
|
}
|
||||||
|
|
||||||
// h! Calculates bearing between two lat/lon points (used for compass)
|
// h! Calculates bearing between two lat/lon points (used for compass)
|
||||||
float calculateBearing(double lat1, double lon1, double lat2, double lon2)
|
float calculateBearing(double lat1, double lon1, double lat2, double lon2)
|
||||||
{
|
{
|
||||||
@ -1907,71 +1932,21 @@ float calculateBearing(double lat1, double lon1, double lat2, double lon2)
|
|||||||
return fmod((initialBearing * RAD_TO_DEG + 360), 360); // Normalize to 0-360°
|
return fmod((initialBearing * RAD_TO_DEG + 360), 360); // Normalize to 0-360°
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shared scroll index state for node screens
|
|
||||||
static int scrollIndex = 0;
|
|
||||||
|
|
||||||
// Helper: Calculates max scroll index based on total entries
|
|
||||||
int calculateMaxScroll(int totalEntries, int visibleRows)
|
int calculateMaxScroll(int totalEntries, int visibleRows)
|
||||||
{
|
{
|
||||||
int totalRows = (totalEntries + 1) / 2;
|
int totalRows = (totalEntries + 1) / 2;
|
||||||
return std::max(0, totalRows - visibleRows);
|
return std::max(0, totalRows - visibleRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Draw vertical scrollbar matching CannedMessageModule style
|
// =============================
|
||||||
void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries, int scrollIndex, int columns, int scrollStartY)
|
// Node Sorting and Scroll Helpers
|
||||||
{
|
// =============================
|
||||||
const int rowHeight = FONT_HEIGHT_SMALL - 3;
|
|
||||||
|
|
||||||
// Visual rows across both columns
|
|
||||||
const int totalVisualRows = (totalEntries + columns - 1) / columns;
|
|
||||||
|
|
||||||
if (totalVisualRows <= visibleNodeRows)
|
|
||||||
return; // Don't draw scrollbar if everything fits
|
|
||||||
|
|
||||||
const int scrollAreaHeight = visibleNodeRows * rowHeight;
|
|
||||||
const int scrollbarX = display->getWidth() - 6;
|
|
||||||
const int scrollbarWidth = 4;
|
|
||||||
|
|
||||||
const int scrollBarHeight = (scrollAreaHeight * visibleNodeRows) / totalVisualRows;
|
|
||||||
const int scrollBarY = scrollStartY + (scrollAreaHeight * scrollIndex) / totalVisualRows;
|
|
||||||
|
|
||||||
display->drawRect(scrollbarX, scrollStartY, scrollbarWidth, scrollAreaHeight);
|
|
||||||
display->fillRect(scrollbarX, scrollBarY, scrollbarWidth, scrollBarHeight);
|
|
||||||
}
|
|
||||||
// Grabs all nodes from the DB and sorts them (favorites and most recently heard first)
|
|
||||||
void retrieveAndSortNodes(std::vector<NodeEntry> &nodeList)
|
|
||||||
{
|
|
||||||
size_t numNodes = nodeDB->getNumMeshNodes();
|
|
||||||
|
|
||||||
for (size_t i = 0; i < numNodes; i++) {
|
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
|
||||||
if (!node || node->num == nodeDB->getNodeNum())
|
|
||||||
continue; // Skip self
|
|
||||||
nodeList.push_back({node, sinceLastSeen(node)});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::sort(nodeList.begin(), nodeList.end(), [](const NodeEntry &a, const NodeEntry &b) {
|
|
||||||
bool aFav = a.node->is_favorite;
|
|
||||||
bool bFav = b.node->is_favorite;
|
|
||||||
if (aFav != bFav)
|
|
||||||
return aFav > bFav;
|
|
||||||
if (a.lastHeard == 0 || a.lastHeard == UINT32_MAX)
|
|
||||||
return false;
|
|
||||||
if (b.lastHeard == 0 || b.lastHeard == UINT32_MAX)
|
|
||||||
return true;
|
|
||||||
return a.lastHeard < b.lastHeard;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Fallback-NodeID if emote is on ShortName for display purposes
|
|
||||||
String getSafeNodeName(meshtastic_NodeInfoLite *node)
|
String getSafeNodeName(meshtastic_NodeInfoLite *node)
|
||||||
{
|
{
|
||||||
String nodeName = "?";
|
String nodeName = "?";
|
||||||
|
|
||||||
if (node->has_user && strlen(node->user.short_name) > 0) {
|
if (node->has_user && strlen(node->user.short_name) > 0) {
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
const char *name = node->user.short_name;
|
const char *name = node->user.short_name;
|
||||||
|
|
||||||
for (size_t i = 0; i < strlen(name); i++) {
|
for (size_t i = 0; i < strlen(name); i++) {
|
||||||
uint8_t c = (uint8_t)name[i];
|
uint8_t c = (uint8_t)name[i];
|
||||||
if (c < 32 || c > 126) {
|
if (c < 32 || c > 126) {
|
||||||
@ -1979,23 +1954,63 @@ String getSafeNodeName(meshtastic_NodeInfoLite *node)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
nodeName = name;
|
nodeName = name;
|
||||||
} else {
|
} else {
|
||||||
// fallback: last 4 hex digits of node ID, no prefix
|
|
||||||
char idStr[6];
|
char idStr[6];
|
||||||
snprintf(idStr, sizeof(idStr), "%04X", (uint16_t)(node->num & 0xFFFF));
|
snprintf(idStr, sizeof(idStr), "%04X", (uint16_t)(node->num & 0xFFFF));
|
||||||
nodeName = String(idStr);
|
nodeName = String(idStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (node->is_favorite) nodeName = "*" + nodeName;
|
||||||
if (node->is_favorite)
|
|
||||||
nodeName = "*" + nodeName;
|
|
||||||
return nodeName;
|
return nodeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draws separator line
|
void retrieveAndSortNodes(std::vector<NodeEntry> &nodeList)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
|
bool hasValidSelf = nodeDB->hasValidPosition(ourNode);
|
||||||
|
|
||||||
|
size_t numNodes = nodeDB->getNumMeshNodes();
|
||||||
|
for (size_t i = 0; i < numNodes; i++) {
|
||||||
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||||
|
if (!node || node->num == nodeDB->getNodeNum()) continue;
|
||||||
|
|
||||||
|
NodeEntry entry;
|
||||||
|
entry.node = node;
|
||||||
|
entry.lastHeard = sinceLastSeen(node);
|
||||||
|
entry.cachedDistance = -1.0f;
|
||||||
|
|
||||||
|
// Pre-calculate distance if we're about to render distance screen
|
||||||
|
if (currentMode == MODE_DISTANCE && hasValidSelf && nodeDB->hasValidPosition(node)) {
|
||||||
|
float lat1 = ourNode->position.latitude_i * 1e-7f;
|
||||||
|
float lon1 = ourNode->position.longitude_i * 1e-7f;
|
||||||
|
float lat2 = node->position.latitude_i * 1e-7f;
|
||||||
|
float lon2 = node->position.longitude_i * 1e-7f;
|
||||||
|
|
||||||
|
float dLat = (lat2 - lat1) * DEG_TO_RAD;
|
||||||
|
float dLon = (lon2 - lon1) * DEG_TO_RAD;
|
||||||
|
float a = sin(dLat / 2) * sin(dLat / 2) + cos(lat1 * DEG_TO_RAD) * cos(lat2 * DEG_TO_RAD) * sin(dLon / 2) * sin(dLon / 2);
|
||||||
|
float c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
||||||
|
entry.cachedDistance = 6371.0f * c; // Earth radius in km
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeList.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(nodeList.begin(), nodeList.end(), [](const NodeEntry &a, const NodeEntry &b) {
|
||||||
|
bool aFav = a.node->is_favorite;
|
||||||
|
bool bFav = b.node->is_favorite;
|
||||||
|
if (aFav != bFav) return aFav > bFav;
|
||||||
|
if (a.lastHeard == 0 || a.lastHeard == UINT32_MAX) return false;
|
||||||
|
if (b.lastHeard == 0 || b.lastHeard == UINT32_MAX) return true;
|
||||||
|
return a.lastHeard < b.lastHeard;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd)
|
void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd)
|
||||||
{
|
{
|
||||||
int columnWidth = display->getWidth() / 2;
|
int columnWidth = display->getWidth() / 2;
|
||||||
@ -2003,6 +2018,23 @@ void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_
|
|||||||
display->drawLine(separatorX, yStart, separatorX, yEnd);
|
display->drawLine(separatorX, yStart, separatorX, yEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries, int scrollIndex, int columns, int scrollStartY)
|
||||||
|
{
|
||||||
|
const int rowHeight = FONT_HEIGHT_SMALL - 3;
|
||||||
|
const int totalVisualRows = (totalEntries + columns - 1) / columns;
|
||||||
|
if (totalVisualRows <= visibleNodeRows) return;
|
||||||
|
const int scrollAreaHeight = visibleNodeRows * rowHeight;
|
||||||
|
const int scrollbarX = display->getWidth() - 6;
|
||||||
|
const int scrollbarWidth = 4;
|
||||||
|
const int scrollBarHeight = (scrollAreaHeight * visibleNodeRows) / totalVisualRows;
|
||||||
|
const int scrollBarY = scrollStartY + (scrollAreaHeight * scrollIndex) / totalVisualRows;
|
||||||
|
display->drawRect(scrollbarX, scrollStartY, scrollbarWidth, scrollAreaHeight);
|
||||||
|
display->fillRect(scrollbarX, scrollBarY, scrollbarWidth, scrollBarHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// Shared Node List Screen Logic
|
||||||
|
// =============================
|
||||||
void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title,
|
void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title,
|
||||||
EntryRenderer renderer, NodeExtrasRenderer extras = nullptr, float heading = 0, double lat = 0,
|
EntryRenderer renderer, NodeExtrasRenderer extras = nullptr, float heading = 0, double lat = 0,
|
||||||
double lon = 0)
|
double lon = 0)
|
||||||
@ -2086,18 +2118,13 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
|
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************
|
// =============================
|
||||||
// * Last Heard Screen *
|
// Shared Dynamic Entry Renderers
|
||||||
// ****************************
|
// =============================
|
||||||
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||||
{
|
{
|
||||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||||
|
int timeOffset = (SCREEN_WIDTH > 128) ? (isLeftCol ? 41 : 45) : (isLeftCol ? 24 : 30);
|
||||||
// Adjust offset based on column and screen width
|
|
||||||
int timeOffset =
|
|
||||||
(SCREEN_WIDTH > 128)
|
|
||||||
? (isLeftCol ? 41 : 45)
|
|
||||||
: (isLeftCol ? 24 : 30); // offset large screen (?Left:Right column), offset small screen (?Left:Right column)
|
|
||||||
|
|
||||||
String nodeName = getSafeNodeName(node);
|
String nodeName = getSafeNodeName(node);
|
||||||
|
|
||||||
@ -2108,12 +2135,8 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
} else {
|
} else {
|
||||||
uint32_t minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
|
uint32_t minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
|
||||||
snprintf(timeStr, sizeof(timeStr), (days > 365 ? "?" : "%d%c"),
|
snprintf(timeStr, sizeof(timeStr), (days > 365 ? "?" : "%d%c"),
|
||||||
(days ? days
|
(days ? days : hours ? hours : minutes),
|
||||||
: hours ? hours
|
(days ? 'd' : hours ? 'h' : 'm'));
|
||||||
: minutes),
|
|
||||||
(days ? 'd'
|
|
||||||
: hours ? 'h'
|
|
||||||
: 'm'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
@ -2130,15 +2153,8 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||||
|
|
||||||
int nameMaxWidth = columnWidth - 25;
|
int nameMaxWidth = columnWidth - 25;
|
||||||
int barsOffset =
|
int barsOffset = (SCREEN_WIDTH > 128) ? (isLeftCol ? 26 : 30) : (isLeftCol ? 17 : 19);
|
||||||
(SCREEN_WIDTH > 128)
|
int hopOffset = (SCREEN_WIDTH > 128) ? (isLeftCol ? 32 : 38) : (isLeftCol ? 18 : 20);
|
||||||
? (isLeftCol ? 26 : 30)
|
|
||||||
: (isLeftCol ? 17 : 19); // offset large screen (?Left:Right column), offset small screen (?Left:Right column)
|
|
||||||
int hopOffset =
|
|
||||||
(SCREEN_WIDTH > 128)
|
|
||||||
? (isLeftCol ? 32 : 38)
|
|
||||||
: (isLeftCol ? 18 : 20); // offset large screen (?Left:Right column), offset small screen (?Left:Right column)
|
|
||||||
|
|
||||||
int barsXOffset = columnWidth - barsOffset;
|
int barsXOffset = columnWidth - barsOffset;
|
||||||
|
|
||||||
String nodeName = getSafeNodeName(node);
|
String nodeName = getSafeNodeName(node);
|
||||||
@ -2156,7 +2172,6 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
display->drawString(hopX, y, hopStr);
|
display->drawString(hopX, y, hopStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signal bars based on SNR
|
|
||||||
int bars = (node->snr > 5) ? 4 : (node->snr > 0) ? 3 : (node->snr > -5) ? 2 : (node->snr > -10) ? 1 : 0;
|
int bars = (node->snr > 5) ? 4 : (node->snr > 0) ? 3 : (node->snr > -5) ? 2 : (node->snr > -10) ? 1 : 0;
|
||||||
int barWidth = 2;
|
int barWidth = 2;
|
||||||
int barStartX = x + barsXOffset;
|
int barStartX = x + barsXOffset;
|
||||||
@ -2182,45 +2197,38 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
char distStr[10] = "";
|
char distStr[10] = "";
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
|
|
||||||
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) {
|
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) {
|
||||||
double lat1 = ourNode->position.latitude_i * 1e-7;
|
double lat1 = ourNode->position.latitude_i * 1e-7;
|
||||||
double lon1 = ourNode->position.longitude_i * 1e-7;
|
double lon1 = ourNode->position.longitude_i * 1e-7;
|
||||||
double lat2 = node->position.latitude_i * 1e-7;
|
double lat2 = node->position.latitude_i * 1e-7;
|
||||||
double lon2 = node->position.longitude_i * 1e-7;
|
double lon2 = node->position.longitude_i * 1e-7;
|
||||||
|
|
||||||
// Haversine formula to calculate distance between two lat/lon points
|
|
||||||
double earthRadiusKm = 6371.0;
|
double earthRadiusKm = 6371.0;
|
||||||
double dLat = (lat2 - lat1) * DEG_TO_RAD;
|
double dLat = (lat2 - lat1) * DEG_TO_RAD;
|
||||||
double dLon = (lon2 - lon1) * DEG_TO_RAD;
|
double dLon = (lon2 - lon1) * DEG_TO_RAD;
|
||||||
|
|
||||||
double a =
|
double a = sin(dLat / 2) * sin(dLat / 2) + cos(lat1 * DEG_TO_RAD) * cos(lat2 * DEG_TO_RAD) * sin(dLon / 2) * sin(dLon / 2);
|
||||||
sin(dLat / 2) * sin(dLat / 2) + cos(lat1 * DEG_TO_RAD) * cos(lat2 * DEG_TO_RAD) * sin(dLon / 2) * sin(dLon / 2);
|
|
||||||
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
|
||||||
double distanceKm = earthRadiusKm * c;
|
double distanceKm = earthRadiusKm * c;
|
||||||
|
|
||||||
// Convert to imperial or metric string based on config
|
|
||||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||||
double miles = distanceKm * 0.621371;
|
double miles = distanceKm * 0.621371;
|
||||||
if (miles < 0.1) {
|
if (miles < 0.1)
|
||||||
snprintf(distStr, sizeof(distStr), "%dft", (int)(miles * 5280));
|
snprintf(distStr, sizeof(distStr), "%dft", (int)(miles * 5280));
|
||||||
} else if (miles < 10.0) {
|
else if (miles < 10.0)
|
||||||
snprintf(distStr, sizeof(distStr), "%.1fmi", miles);
|
snprintf(distStr, sizeof(distStr), "%.1fmi", miles);
|
||||||
} else {
|
else
|
||||||
snprintf(distStr, sizeof(distStr), "%dmi", (int)miles);
|
snprintf(distStr, sizeof(distStr), "%dmi", (int)miles);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (distanceKm < 1.0) {
|
if (distanceKm < 1.0)
|
||||||
snprintf(distStr, sizeof(distStr), "%dm", (int)(distanceKm * 1000));
|
snprintf(distStr, sizeof(distStr), "%dm", (int)(distanceKm * 1000));
|
||||||
} else if (distanceKm < 10.0) {
|
else if (distanceKm < 10.0)
|
||||||
snprintf(distStr, sizeof(distStr), "%.1fkm", distanceKm);
|
snprintf(distStr, sizeof(distStr), "%.1fkm", distanceKm);
|
||||||
} else {
|
else
|
||||||
snprintf(distStr, sizeof(distStr), "%dkm", (int)distanceKm);
|
snprintf(distStr, sizeof(distStr), "%dkm", (int)distanceKm);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render node name and distance
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
display->drawStringMaxWidth(x, y, nameMaxWidth, nodeName);
|
display->drawStringMaxWidth(x, y, nameMaxWidth, nodeName);
|
||||||
@ -2231,77 +2239,74 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_EINK
|
// =============================
|
||||||
|
// Dynamic Unified Entry Renderer
|
||||||
// Public screen function: shows how recently nodes were heard
|
// =============================
|
||||||
static void drawLastHeardScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void drawEntryDynamic(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||||
{
|
{
|
||||||
drawNodeListScreen(display, state, x, y, "Node List", drawEntryLastHeard);
|
switch (currentMode) {
|
||||||
|
case MODE_LAST_HEARD:
|
||||||
|
drawEntryLastHeard(display, node, x, y, columnWidth);
|
||||||
|
break;
|
||||||
|
case MODE_HOP_SIGNAL:
|
||||||
|
drawEntryHopSignal(display, node, x, y, columnWidth);
|
||||||
|
break;
|
||||||
|
case MODE_DISTANCE:
|
||||||
|
drawNodeDistance(display, node, x, y, columnWidth);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break; // Silences warning for MODE_COUNT or unexpected values
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public screen function: shows hop count + signal strength
|
const char* getCurrentModeTitle()
|
||||||
static void drawHopSignalScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
{
|
||||||
drawNodeListScreen(display, state, x, y, "Hop|Sig", drawEntryHopSignal);
|
switch (currentMode) {
|
||||||
|
case MODE_LAST_HEARD: return "Node List";
|
||||||
|
case MODE_HOP_SIGNAL: return "Hop|Sig";
|
||||||
|
case MODE_DISTANCE: return "Distances";
|
||||||
|
default: return "Nodes";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public screen function: shows distance to each node
|
// =============================
|
||||||
static void drawDistanceScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
// OLED/TFT Version (cycles every few seconds)
|
||||||
{
|
// =============================
|
||||||
drawNodeListScreen(display, state, x, y, "Distances", drawNodeDistance);
|
#ifndef USE_EINK
|
||||||
}
|
static void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
|
||||||
#endif // USE_EINK
|
|
||||||
|
|
||||||
// Array of rendering functions to rotate through
|
|
||||||
static EntryRenderer entryRenderers[] = {
|
|
||||||
drawEntryLastHeard, // Shows time since last heard
|
|
||||||
drawEntryHopSignal, // Shows hop count and signal bars
|
|
||||||
drawNodeDistance // New: Shows physical distance
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char *titles[] = {
|
|
||||||
"Last Heard",
|
|
||||||
"Hop|Sig",
|
|
||||||
"Distances" // Corresponding title
|
|
||||||
};
|
|
||||||
|
|
||||||
// Count of total renderers (auto-sized)
|
|
||||||
static const int NUM_RENDERERS = sizeof(entryRenderers) / sizeof(entryRenderers[0]);
|
|
||||||
|
|
||||||
// Tracks last time a switch occurred
|
|
||||||
static unsigned long lastSwitchTime = 0;
|
|
||||||
|
|
||||||
// Index of the currently active renderer
|
|
||||||
static int currentRendererIndex = 0;
|
|
||||||
|
|
||||||
// How long to show each view (milliseconds)
|
|
||||||
static const unsigned long RENDER_INTERVAL_MS = 2000;
|
|
||||||
|
|
||||||
|
|
||||||
// Master function to draw the rotating node list screens
|
|
||||||
static void drawCyclingNodeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
{
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
|
|
||||||
// Reset to first view on initial entry to screen
|
// Always start with MODE_LAST_HEARD on screen entry
|
||||||
if (state->ticksSinceLastStateSwitch == 0) {
|
if (state->ticksSinceLastStateSwitch == 0) {
|
||||||
currentRendererIndex = 0;
|
currentMode = MODE_LAST_HEARD;
|
||||||
lastSwitchTime = now;
|
lastModeSwitchTime = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⏱️ Cycle content every interval
|
if (now - lastModeSwitchTime >= getModeCycleIntervalMs()) {
|
||||||
if (now - lastSwitchTime >= RENDER_INTERVAL_MS) {
|
lastModeSwitchTime = now;
|
||||||
lastSwitchTime = now;
|
currentMode = static_cast<NodeListMode>((currentMode + 1) % MODE_COUNT);
|
||||||
currentRendererIndex = (currentRendererIndex + 1) % NUM_RENDERERS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the correct renderer and title for the current screen
|
const char* title = getCurrentModeTitle();
|
||||||
EntryRenderer currentRenderer = entryRenderers[currentRendererIndex];
|
drawNodeListScreen(display, state, x, y, title, drawEntryDynamic);
|
||||||
|
|
||||||
// Show the screen
|
|
||||||
drawNodeListScreen(display, state, x, y, titles[currentRendererIndex], currentRenderer);
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// E-Ink Version (mode set once per boot)
|
||||||
|
// =============================
|
||||||
|
#ifdef USE_EINK
|
||||||
|
static void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
{
|
||||||
|
if (state->ticksSinceLastStateSwitch == 0) {
|
||||||
|
currentMode = MODE_LAST_HEARD;
|
||||||
|
}
|
||||||
|
const char* title = getCurrentModeTitle();
|
||||||
|
drawNodeListScreen(display, state, x, y, title, drawEntryDynamic);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// Helper function: Draw a single node entry for Node List (Modified for Compass Screen)
|
// Helper function: Draw a single node entry for Node List (Modified for Compass Screen)
|
||||||
void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||||
@ -3501,7 +3506,7 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
|
|
||||||
|
|
||||||
normalFrames[numframes++] = drawDeviceFocused;
|
normalFrames[numframes++] = drawDeviceFocused;
|
||||||
normalFrames[numframes++] = drawCyclingNodeScreen;
|
normalFrames[numframes++] = drawDynamicNodeListScreen;
|
||||||
|
|
||||||
// Show detailed node views only on E-Ink builds
|
// Show detailed node views only on E-Ink builds
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
@ -3509,6 +3514,7 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
normalFrames[numframes++] = drawHopSignalScreen;
|
normalFrames[numframes++] = drawHopSignalScreen;
|
||||||
normalFrames[numframes++] = drawDistanceScreen;
|
normalFrames[numframes++] = drawDistanceScreen;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
normalFrames[numframes++] = drawNodeListWithCompasses;
|
normalFrames[numframes++] = drawNodeListWithCompasses;
|
||||||
normalFrames[numframes++] = drawCompassAndLocationScreen;
|
normalFrames[numframes++] = drawCompassAndLocationScreen;
|
||||||
normalFrames[numframes++] = drawLoRaFocused;
|
normalFrames[numframes++] = drawLoRaFocused;
|
||||||
|
Loading…
Reference in New Issue
Block a user