mirror of
https://github.com/meshtastic/firmware.git
synced 2025-08-10 07:35:00 +00:00
Node list cleanup and unification
This commit is contained in:
parent
2b7bc6696f
commit
ae47de152c
@ -1939,93 +1939,11 @@ void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_
|
||||
display->drawLine(separatorX, yStart, separatorX, yEnd);
|
||||
}
|
||||
|
||||
// Draws node name with how long ago it was last heard from
|
||||
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||
{
|
||||
int screenWidth = display->getWidth();
|
||||
bool isLeftCol = (x < screenWidth / 2);
|
||||
|
||||
// Adjust offset based on column and screen width
|
||||
int timeOffset =
|
||||
(screenWidth > 128)
|
||||
? (isLeftCol ? 41 : 45)
|
||||
: (isLeftCol ? 24 : 30); // offset large screen (?Left:Right column), offset small screen (?Left:Right column)
|
||||
|
||||
String nodeName = getSafeNodeName(node);
|
||||
|
||||
char timeStr[10];
|
||||
uint32_t seconds = sinceLastSeen(node);
|
||||
if (seconds == 0 || seconds == UINT32_MAX) {
|
||||
snprintf(timeStr, sizeof(timeStr), "? ");
|
||||
} else {
|
||||
uint32_t minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
|
||||
snprintf(timeStr, sizeof(timeStr), (days > 365 ? "?" : "%d%c"),
|
||||
(days ? days
|
||||
: hours ? hours
|
||||
: minutes),
|
||||
(days ? 'd'
|
||||
: hours ? 'h'
|
||||
: 'm'));
|
||||
}
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(x, y, nodeName);
|
||||
display->drawString(x + columnWidth - timeOffset, y, timeStr);
|
||||
}
|
||||
// Draws each node's name, hop count, and signal bars
|
||||
void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||
{
|
||||
int screenWidth = display->getWidth();
|
||||
bool isLeftCol = (x < screenWidth / 2);
|
||||
|
||||
int nameMaxWidth = columnWidth - 25;
|
||||
int barsOffset =
|
||||
(screenWidth > 128)
|
||||
? (isLeftCol ? 26 : 30)
|
||||
: (isLeftCol ? 17 : 19); // offset large screen (?Left:Right column), offset small screen (?Left:Right column)
|
||||
int hopOffset =
|
||||
(screenWidth > 128)
|
||||
? (isLeftCol ? 32 : 38)
|
||||
: (isLeftCol ? 18 : 20); // offset large screen (?Left:Right column), offset small screen (?Left:Right column)
|
||||
|
||||
int barsXOffset = columnWidth - barsOffset;
|
||||
|
||||
String nodeName = getSafeNodeName(node);
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawStringMaxWidth(x, y, nameMaxWidth, nodeName);
|
||||
|
||||
char hopStr[6] = "";
|
||||
if (node->has_hops_away && node->hops_away > 0)
|
||||
snprintf(hopStr, sizeof(hopStr), "[%d]", node->hops_away);
|
||||
|
||||
if (hopStr[0] != '\0') {
|
||||
int hopX = x + columnWidth - hopOffset - display->getStringWidth(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 barWidth = 2;
|
||||
int barStartX = x + barsXOffset;
|
||||
int barStartY = y + (FONT_HEIGHT_SMALL / 2) + 2;
|
||||
|
||||
for (int b = 0; b < 4; b++) {
|
||||
if (b < bars) {
|
||||
int height = (b * 2);
|
||||
display->fillRect(barStartX + (b * (barWidth + 1)), barStartY - height, barWidth, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Typedef for passing different render functions into one reusable screen function
|
||||
typedef void (*EntryRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int);
|
||||
typedef void (*NodeExtrasRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int columnWidth, float heading, double lat, double lon);
|
||||
|
||||
// Shared function that renders all node screens (LastHeard, Hop/Signal)
|
||||
void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title,
|
||||
EntryRenderer renderer)
|
||||
void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y,
|
||||
const char *title, EntryRenderer renderer, NodeExtrasRenderer extras = nullptr)
|
||||
{
|
||||
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
|
||||
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
|
||||
@ -2101,171 +2019,95 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
|
||||
}
|
||||
|
||||
|
||||
// Public screen function: shows how recently nodes were heard
|
||||
static void drawLastHeardScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
drawNodeListScreen(display, state, x, y, "Node List", drawEntryLastHeard);
|
||||
}
|
||||
|
||||
// Public screen function: shows hop count + signal strength
|
||||
static void drawHopSignalScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
drawNodeListScreen(display, state, x, y, "Hop|Sig", drawEntryHopSignal);
|
||||
}
|
||||
|
||||
// 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)
|
||||
// ****************************
|
||||
// * Last Heard Screen *
|
||||
// ****************************
|
||||
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||
{
|
||||
int screenWidth = display->getWidth();
|
||||
bool isLeftCol = (x < screenWidth / 2);
|
||||
|
||||
// Adjust max text width depending on column and screen width
|
||||
int nameMaxWidth = columnWidth - (screenWidth > 128 ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
||||
// Adjust offset based on column and screen width
|
||||
int timeOffset =
|
||||
(screenWidth > 128)
|
||||
? (isLeftCol ? 41 : 45)
|
||||
: (isLeftCol ? 24 : 30); // offset large screen (?Left:Right column), offset small screen (?Left:Right column)
|
||||
|
||||
String nodeName = getSafeNodeName(node);
|
||||
|
||||
char timeStr[10];
|
||||
uint32_t seconds = sinceLastSeen(node);
|
||||
if (seconds == 0 || seconds == UINT32_MAX) {
|
||||
snprintf(timeStr, sizeof(timeStr), "? ");
|
||||
} else {
|
||||
uint32_t minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
|
||||
snprintf(timeStr, sizeof(timeStr), (days > 365 ? "?" : "%d%c"),
|
||||
(days ? days
|
||||
: hours ? hours
|
||||
: minutes),
|
||||
(days ? 'd'
|
||||
: hours ? 'h'
|
||||
: 'm'));
|
||||
}
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(x, y, nodeName);
|
||||
display->drawString(x + columnWidth - timeOffset, y, timeStr);
|
||||
}
|
||||
|
||||
// ****************************
|
||||
// * Hops / Signal Screen *
|
||||
// ****************************
|
||||
void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||
{
|
||||
int screenWidth = display->getWidth();
|
||||
bool isLeftCol = (x < screenWidth / 2);
|
||||
|
||||
int nameMaxWidth = columnWidth - 25;
|
||||
int barsOffset =
|
||||
(screenWidth > 128)
|
||||
? (isLeftCol ? 26 : 30)
|
||||
: (isLeftCol ? 17 : 19); // offset large screen (?Left:Right column), offset small screen (?Left:Right column)
|
||||
int hopOffset =
|
||||
(screenWidth > 128)
|
||||
? (isLeftCol ? 32 : 38)
|
||||
: (isLeftCol ? 18 : 20); // offset large screen (?Left:Right column), offset small screen (?Left:Right column)
|
||||
|
||||
int barsXOffset = columnWidth - barsOffset;
|
||||
|
||||
String nodeName = getSafeNodeName(node);
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawStringMaxWidth(x, y, nameMaxWidth, nodeName);
|
||||
|
||||
char hopStr[6] = "";
|
||||
if (node->has_hops_away && node->hops_away > 0)
|
||||
snprintf(hopStr, sizeof(hopStr), "[%d]", node->hops_away);
|
||||
|
||||
if (hopStr[0] != '\0') {
|
||||
int hopX = x + columnWidth - hopOffset - display->getStringWidth(hopStr);
|
||||
display->drawString(hopX, y, hopStr);
|
||||
}
|
||||
|
||||
// Extra compass element drawer (injects compass arrows)
|
||||
typedef void (*CompassExtraRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int columnWidth, float myHeading,
|
||||
double userLat, double userLon);
|
||||
// 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 barWidth = 2;
|
||||
int barStartX = x + barsXOffset;
|
||||
int barStartY = y + (FONT_HEIGHT_SMALL / 2) + 2;
|
||||
|
||||
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float myHeading,
|
||||
double userLat, double userLon)
|
||||
{
|
||||
if (!nodeDB->hasValidPosition(node))
|
||||
return;
|
||||
|
||||
int screenWidth = display->getWidth();
|
||||
bool isLeftCol = (x < screenWidth / 2);
|
||||
|
||||
double nodeLat = node->position.latitude_i * 1e-7;
|
||||
double nodeLon = node->position.longitude_i * 1e-7;
|
||||
float bearingToNode = calculateBearing(userLat, userLon, nodeLat, nodeLon);
|
||||
float relativeBearing = fmod((bearingToNode - myHeading + 360), 360);
|
||||
float arrowAngle = relativeBearing * DEG_TO_RAD;
|
||||
|
||||
// Adaptive offset for compass icon based on screen width + column
|
||||
int arrowXOffset = (screenWidth > 128) ? (isLeftCol ? 22 : 24) : (isLeftCol ? 12 : 18);
|
||||
|
||||
int compassX = x + columnWidth - arrowXOffset;
|
||||
int compassY = y + FONT_HEIGHT_SMALL / 2;
|
||||
int size = FONT_HEIGHT_SMALL / 2 - 2;
|
||||
int arrowLength = size - 2;
|
||||
|
||||
int xEnd = compassX + arrowLength * cos(arrowAngle);
|
||||
int yEnd = compassY - arrowLength * sin(arrowAngle);
|
||||
|
||||
display->fillCircle(compassX, compassY, size);
|
||||
display->drawCircle(compassX, compassY, size);
|
||||
display->drawLine(compassX, compassY, xEnd, yEnd);
|
||||
for (int b = 0; b < 4; b++) {
|
||||
if (b < bars) {
|
||||
int height = (b * 2);
|
||||
display->fillRect(barStartX + (b * (barWidth + 1)), barStartY - height, barWidth, height);
|
||||
}
|
||||
|
||||
// Generic node+compass renderer (like drawNodeListScreen but with compass support)
|
||||
void drawNodeListWithExtrasScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title,
|
||||
EntryRenderer renderer, CompassExtraRenderer extras)
|
||||
{
|
||||
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
|
||||
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
|
||||
|
||||
int columnWidth = display->getWidth() / 2;
|
||||
int screenWidth = display->getWidth();
|
||||
|
||||
display->clear();
|
||||
|
||||
// === Draw common header (battery + time)
|
||||
drawCommonHeader(display, x, y);
|
||||
|
||||
// === Draw title inside header (centered)
|
||||
const int highlightHeight = COMMON_HEADER_HEIGHT;
|
||||
const int textY = y + 2 + (highlightHeight - FONT_HEIGHT_SMALL) / 2;
|
||||
const int centerX = x + screenWidth / 2;
|
||||
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED)
|
||||
display->setColor(BLACK);
|
||||
|
||||
display->drawString(centerX, textY, title);
|
||||
if (config.display.heading_bold)
|
||||
display->drawString(centerX + 1, textY, title);
|
||||
|
||||
display->setColor(WHITE);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
// === Space below header
|
||||
y += COMMON_HEADER_HEIGHT;
|
||||
|
||||
std::vector<NodeEntry> nodeList;
|
||||
retrieveAndSortNodes(nodeList);
|
||||
|
||||
int totalEntries = nodeList.size();
|
||||
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
|
||||
int visibleNodeRows = totalRowsAvailable;
|
||||
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
double userLat = 0.0, userLon = 0.0;
|
||||
bool hasUserPosition = nodeDB->hasValidPosition(ourNode);
|
||||
if (hasUserPosition) {
|
||||
userLat = ourNode->position.latitude_i * 1e-7;
|
||||
userLon = ourNode->position.longitude_i * 1e-7;
|
||||
}
|
||||
|
||||
float myHeading = screen->hasHeading() ? screen->getHeading() : 0.0f;
|
||||
|
||||
int startIndex = scrollIndex * visibleNodeRows * 2;
|
||||
int endIndex = std::min(startIndex + visibleNodeRows * 2, totalEntries);
|
||||
|
||||
int yOffset = 0;
|
||||
int col = 0;
|
||||
int lastNodeY = y;
|
||||
int shownCount = 0;
|
||||
|
||||
for (int i = startIndex; i < endIndex; ++i) {
|
||||
int xPos = x + (col * columnWidth);
|
||||
int yPos = y + yOffset;
|
||||
|
||||
renderer(display, nodeList[i].node, xPos, yPos, columnWidth);
|
||||
|
||||
if (hasUserPosition && extras) {
|
||||
extras(display, nodeList[i].node, xPos, yPos, columnWidth, myHeading, userLat, userLon);
|
||||
}
|
||||
|
||||
lastNodeY = std::max(lastNodeY, yPos + FONT_HEIGHT_SMALL);
|
||||
yOffset += rowYOffset;
|
||||
shownCount++;
|
||||
|
||||
if (y + yOffset > display->getHeight() - FONT_HEIGHT_SMALL) {
|
||||
yOffset = 0;
|
||||
col++;
|
||||
if (col > 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// === Draw column separator
|
||||
if (shownCount > 0) {
|
||||
const int firstNodeY = y + 3; // where the first node row starts
|
||||
drawColumnSeparator(display, x, firstNodeY, lastNodeY);
|
||||
}
|
||||
|
||||
const int scrollStartY = y + 3;
|
||||
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
|
||||
}
|
||||
// Public screen entry for compass
|
||||
static void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
drawNodeListWithExtrasScreen(display, state, x, y, "Bearings", drawEntryCompass, drawCompassArrow);
|
||||
}
|
||||
|
||||
// ********************************
|
||||
// * Node List Distance Screen *
|
||||
// ********************************
|
||||
|
||||
// **************************
|
||||
// * Distance Screen *
|
||||
// **************************
|
||||
void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||
{
|
||||
int screenWidth = display->getWidth();
|
||||
@ -2322,11 +2164,71 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
}
|
||||
}
|
||||
|
||||
static void drawDistanceScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// Public screen function: shows how recently nodes were heard
|
||||
static void drawLastHeardScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) {
|
||||
drawNodeListScreen(display, state, x, y, "Node List", drawEntryLastHeard);
|
||||
}
|
||||
|
||||
// Public screen function: shows hop count + signal strength
|
||||
static void drawHopSignalScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) {
|
||||
drawNodeListScreen(display, state, x, y, "Hop|Sig", drawEntryHopSignal);
|
||||
}
|
||||
|
||||
static void drawDistanceScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) {
|
||||
drawNodeListScreen(display, state, x, y, "Distances", drawNodeDistance);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
int screenWidth = display->getWidth();
|
||||
bool isLeftCol = (x < screenWidth / 2);
|
||||
|
||||
// Adjust max text width depending on column and screen width
|
||||
int nameMaxWidth = columnWidth - (screenWidth > 128 ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
||||
|
||||
String nodeName = getSafeNodeName(node);
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawStringMaxWidth(x, y, nameMaxWidth, nodeName);
|
||||
}
|
||||
|
||||
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float myHeading,
|
||||
double userLat, double userLon)
|
||||
{
|
||||
if (!nodeDB->hasValidPosition(node))
|
||||
return;
|
||||
|
||||
int screenWidth = display->getWidth();
|
||||
bool isLeftCol = (x < screenWidth / 2);
|
||||
|
||||
double nodeLat = node->position.latitude_i * 1e-7;
|
||||
double nodeLon = node->position.longitude_i * 1e-7;
|
||||
float bearingToNode = calculateBearing(userLat, userLon, nodeLat, nodeLon);
|
||||
float relativeBearing = fmod((bearingToNode - myHeading + 360), 360);
|
||||
float arrowAngle = relativeBearing * DEG_TO_RAD;
|
||||
|
||||
// Adaptive offset for compass icon based on screen width + column
|
||||
int arrowXOffset = (screenWidth > 128) ? (isLeftCol ? 22 : 24) : (isLeftCol ? 12 : 18);
|
||||
|
||||
int compassX = x + columnWidth - arrowXOffset;
|
||||
int compassY = y + FONT_HEIGHT_SMALL / 2;
|
||||
int size = FONT_HEIGHT_SMALL / 2 - 2;
|
||||
int arrowLength = size - 2;
|
||||
|
||||
int xEnd = compassX + arrowLength * cos(arrowAngle);
|
||||
int yEnd = compassY - arrowLength * sin(arrowAngle);
|
||||
|
||||
display->fillCircle(compassX, compassY, size);
|
||||
display->drawCircle(compassX, compassY, size);
|
||||
display->drawLine(compassX, compassY, xEnd, yEnd);
|
||||
}
|
||||
// Public screen entry for compass
|
||||
static void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) {
|
||||
drawNodeListScreen(display, state, x, y, "Bearings", drawEntryCompass, drawCompassArrow);
|
||||
}
|
||||
|
||||
// ****************************
|
||||
// * Device Focused Screen *
|
||||
// ****************************
|
||||
@ -3422,20 +3324,20 @@ void Screen::setFrames(FrameFocus focus)
|
||||
|
||||
// then all the nodes
|
||||
// We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens
|
||||
// size_t numToShow = min(numMeshNodes, 4U);
|
||||
// for (size_t i = 0; i < numToShow; i++)
|
||||
// normalFrames[numframes++] = drawNodeInfo;
|
||||
size_t numToShow = min(numMeshNodes, 4U);
|
||||
for (size_t i = 0; i < numToShow; i++)
|
||||
normalFrames[numframes++] = drawNodeInfo;
|
||||
|
||||
// then the debug info
|
||||
//
|
||||
|
||||
// Since frames are basic function pointers, we have to use a helper to
|
||||
// call a method on debugInfo object.
|
||||
// fsi.positions.log = numframes;
|
||||
// normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline;
|
||||
fsi.positions.log = numframes;
|
||||
normalFrames[numframes++] = &Screen::drawDebugInfoTrampoline;
|
||||
|
||||
// call a method on debugInfoScreen object (for more details)
|
||||
// fsi.positions.settings = numframes;
|
||||
// normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline;
|
||||
fsi.positions.settings = numframes;
|
||||
normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline;
|
||||
|
||||
fsi.positions.wifi = numframes;
|
||||
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
||||
|
Loading…
Reference in New Issue
Block a user