From bd20c742873377ed4c3532deabd68154a3691afd Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Sat, 29 Mar 2025 04:21:43 -0400 Subject: [PATCH] Added scrollbar code for node list screens --- src/graphics/Screen.cpp | 84 ++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 3c004b9e7..020a9b1d6 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1598,6 +1598,31 @@ float calculateBearing(double lat1, double lon1, double lat2, double lon2) { 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 totalRows = (totalEntries + 1) / 2; + 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 rowYOffset) { + int totalPages = (totalEntries + columns - 1) / columns; + if (totalPages <= visibleNodeRows) return; // no scrollbar needed + + int scrollAreaHeight = visibleNodeRows * (FONT_HEIGHT_SMALL - 3); // true pixel height used per row + int scrollbarX = display->getWidth() - 6; + int scrollbarWidth = 4; + + int scrollBarHeight = (scrollAreaHeight * visibleNodeRows) / totalPages; + int scrollBarY = rowYOffset + (scrollAreaHeight * scrollIndex) / totalPages; + + display->drawRect(scrollbarX, rowYOffset, 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 &nodeList) { size_t numNodes = nodeDB->getNumMeshNodes(); @@ -1756,8 +1781,9 @@ typedef void (*EntryRenderer)(OLEDDisplay*, meshtastic_NodeInfoLite*, int16_t, i // 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) { int columnWidth = display->getWidth() / 2; - int yOffset = FONT_HEIGHT_SMALL - 3; - int col = 0, lastNodeY = y; + int totalRowsAvailable = (display->getHeight() - y - FONT_HEIGHT_SMALL) / (FONT_HEIGHT_SMALL - 3); + int visibleNodeRows = std::min(6, totalRowsAvailable); + int rowYOffset = FONT_HEIGHT_SMALL - 3; display->clear(); drawScreenHeader(display, title, x, y); @@ -1765,20 +1791,35 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t std::vector nodeList; retrieveAndSortNodes(nodeList); - for (const auto &entry : nodeList) { + int totalEntries = nodeList.size(); + int maxScroll = calculateMaxScroll(totalEntries, visibleNodeRows); + scrollIndex = std::min(scrollIndex, maxScroll); + + int startIndex = scrollIndex * visibleNodeRows * 2; + int endIndex = std::min(startIndex + visibleNodeRows * 2, totalEntries); + + int yOffset = rowYOffset; + int col = 0; + int lastNodeY = y; + int shownCount = 0; + + for (int i = startIndex; i < endIndex; ++i) { int xPos = x + (col * columnWidth); - renderer(display, entry.node, xPos, y + yOffset, columnWidth); + int yPos = y + yOffset; + renderer(display, nodeList[i].node, xPos, yPos, columnWidth); lastNodeY = std::max(lastNodeY, y + yOffset + FONT_HEIGHT_SMALL); yOffset += FONT_HEIGHT_SMALL - 3; + shownCount++; if (y + yOffset > display->getHeight() - FONT_HEIGHT_SMALL) { - yOffset = FONT_HEIGHT_SMALL - 3; + yOffset = rowYOffset; col++; if (col > 1) break; } } - // Draw separator between columns + drawColumnSeparator(display, x, y + FONT_HEIGHT_SMALL - 2, lastNodeY); + drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, rowYOffset); } // Public screen function: shows how recently nodes were heard @@ -1845,8 +1886,9 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16 void drawNodeListWithExtrasScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title, EntryRenderer renderer, CompassExtraRenderer extras) { int columnWidth = display->getWidth() / 2; - int yOffset = FONT_HEIGHT_SMALL - 3; - int col = 0, lastNodeY = y; + int totalRowsAvailable = (display->getHeight() - y - FONT_HEIGHT_SMALL) / (FONT_HEIGHT_SMALL - 3); + int visibleNodeRows = std::min(6, totalRowsAvailable); + int rowYOffset = FONT_HEIGHT_SMALL - 3; display->clear(); drawScreenHeader(display, title, x, y); @@ -1854,6 +1896,10 @@ void drawNodeListWithExtrasScreen(OLEDDisplay *display, OLEDDisplayUiState *stat std::vector nodeList; retrieveAndSortNodes(nodeList); + int totalEntries = nodeList.size(); + int maxScroll = calculateMaxScroll(totalEntries, visibleNodeRows); + scrollIndex = std::min(scrollIndex, maxScroll); + meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); double userLat = 0.0, userLon = 0.0; bool hasUserPosition = nodeDB->hasValidPosition(ourNode); @@ -1864,27 +1910,40 @@ void drawNodeListWithExtrasScreen(OLEDDisplay *display, OLEDDisplayUiState *stat float myHeading = screen->hasHeading() ? screen->getHeading() : 0.0f; - for (const auto &entry : nodeList) { + int startIndex = scrollIndex * visibleNodeRows * 2; + int endIndex = std::min(startIndex + visibleNodeRows * 2, totalEntries); + + int yOffset = rowYOffset; + int col = 0; + int lastNodeY = y; + int shownCount = 0; + + for (int i = startIndex; i < endIndex; ++i) { int xPos = x + (col * columnWidth); - renderer(display, entry.node, xPos, y + yOffset, columnWidth); + int yPos = y + yOffset; + + renderer(display, nodeList[i].node, xPos, yPos, columnWidth); if (hasUserPosition && extras) { - extras(display, entry.node, xPos, y + yOffset, columnWidth, myHeading, userLat, userLon); + extras(display, nodeList[i].node, xPos, yPos, columnWidth, myHeading, userLat, userLon); } lastNodeY = std::max(lastNodeY, y + yOffset + FONT_HEIGHT_SMALL); yOffset += FONT_HEIGHT_SMALL - 3; + shownCount++; if (y + yOffset > display->getHeight() - FONT_HEIGHT_SMALL) { - yOffset = FONT_HEIGHT_SMALL - 3; + yOffset = rowYOffset; col++; if (col > 1) break; } } drawColumnSeparator(display, x, y + FONT_HEIGHT_SMALL - 2, lastNodeY); + drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, rowYOffset); } + // 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); @@ -2048,7 +2107,6 @@ static void drawDefaultScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i } - #if defined(ESP_PLATFORM) && defined(USE_ST7789) SPIClass SPI1(HSPI); #endif