Compare commits

...

7 Commits

Author SHA1 Message Date
Jason P
2dc54cdd4b Dynamic scaling of column counts based upon screen size, clean up box drawing 2025-12-11 22:42:40 -06:00
HarukiToreda
1b7104b1e2 Page counters 2025-12-11 23:08:56 -05:00
HarukiToreda
ef99939d6f Pagination fix for Latest to oldest per page 2025-12-11 22:43:21 -05:00
Jason P
4abd3f9a1f Merge branch 'develop' into multi-message-Storage 2025-12-11 20:57:06 -06:00
HarukiToreda
cfea55d77d Add scrolling to Node list 2025-12-11 20:28:43 -05:00
Austin
bcfe069997 Optimize builds to reduce duplicate dependency checks (#8943)
'mtjson' will now build all required pieces when they don't exist
2025-12-11 19:01:31 -06:00
Jason P
71bc99938c Short or Long Names for everyone! 2025-12-11 17:22:17 -06:00
11 changed files with 207 additions and 82 deletions

View File

@@ -22,7 +22,7 @@ export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION basename=firmware-$1-$VERSION
pio run --environment $1 # -v pio run --environment $1 -t mtjson # -v
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
@@ -32,20 +32,10 @@ cp $BUILDDIR/$basename.factory.bin $OUTDIR/$basename.factory.bin
echo "Copying ESP32 update bin file" echo "Copying ESP32 update bin file"
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
echo "Building Filesystem for ESP32 targets" echo "Copying Filesystem for ESP32 targets"
# If you want to build the webui, uncomment the following lines
# pio run --environment $1 -t buildfs
# cp .pio/build/$1/littlefs.bin $OUTDIR/littlefswebui-$1-$VERSION.bin
# # Remove webserver files from the filesystem and rebuild
# ls -l data/static # Diagnostic list of files
# rm -rf data/static
pio run --environment $1 -t buildfs --disable-auto-clean
cp $BUILDDIR/littlefs-$1-$VERSION.bin $OUTDIR/littlefs-$1-$VERSION.bin cp $BUILDDIR/littlefs-$1-$VERSION.bin $OUTDIR/littlefs-$1-$VERSION.bin
cp bin/device-install.* $OUTDIR/ cp bin/device-install.* $OUTDIR/
cp bin/device-update.* $OUTDIR/ cp bin/device-update.* $OUTDIR/
# Generate the manifest file echo "Copying manifest"
echo "Generating Meshtastic manifest"
TIMEFORMAT="Generated manifest in %E seconds"
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json

View File

@@ -22,7 +22,7 @@ export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION basename=firmware-$1-$VERSION
pio run --environment $1 # -v pio run --environment $1 -t mtjson # -v
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
@@ -47,8 +47,5 @@ if (echo $1 | grep -q "rak4631"); then
cp $SRCHEX $OUTDIR/ cp $SRCHEX $OUTDIR/
fi fi
# Generate the manifest file echo "Copying manifest"
echo "Generating Meshtastic manifest"
TIMEFORMAT="Generated manifest in %E seconds"
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json

View File

@@ -22,15 +22,12 @@ export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION basename=firmware-$1-$VERSION
pio run --environment $1 # -v pio run --environment $1 -t mtjson # -v
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
echo "Copying uf2 file" echo "Copying uf2 file"
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2 cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
# Generate the manifest file echo "Copying manifest"
echo "Generating Meshtastic manifest"
TIMEFORMAT="Generated manifest in %E seconds"
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json

View File

@@ -22,15 +22,12 @@ export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION basename=firmware-$1-$VERSION
pio run --environment $1 # -v pio run --environment $1 -t mtjson # -v
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
echo "Copying STM32 bin file" echo "Copying STM32 bin file"
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
# Generate the manifest file echo "Copying manifest"
echo "Generating Meshtastic manifest"
TIMEFORMAT="Generated manifest in %E seconds"
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json

View File

@@ -159,20 +159,22 @@ def load_boot_logo(source, target, env):
# Load the boot logo on TFT builds # Load the boot logo on TFT builds
if ("HAS_TFT", 1) in env.get("CPPDEFINES", []): if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
env.AddPreAction('$BUILD_DIR/littlefs.bin', load_boot_logo) env.AddPreAction(f"$BUILD_DIR/{lfsbin}", load_boot_logo)
# Rename (mv) littlefs.bin to include the PROGNAME mtjson_deps = ["buildprog"]
# This ensures the littlefs.bin is named consistently with the firmware if platform.name == "espressif32":
env.AddPostAction('$BUILD_DIR/littlefs.bin', env.VerboseAction( # Build littlefs image as part of mtjson target
f'mv $BUILD_DIR/littlefs.bin $BUILD_DIR/{lfsbin}', # Equivalent to `pio run -t buildfs`
f'Renaming littlefs.bin to {lfsbin}' target_lfs = env.DataToBin(
)) join("$BUILD_DIR", "${ESP32_FS_IMAGE_NAME}"), "$PROJECT_DATA_DIR"
)
mtjson_deps.append(target_lfs)
env.AddCustomTarget( env.AddCustomTarget(
name="mtjson", name="mtjson",
dependencies=None, dependencies=mtjson_deps,
actions=[manifest_gather], actions=[manifest_gather],
title="Meshtastic Manifest", title="Meshtastic Manifest",
description="Generating Meshtastic manifest JSON + Checksums", description="Generating Meshtastic manifest JSON + Checksums",
always_build=True, always_build=False,
) )

View File

@@ -11,6 +11,9 @@ else:
prefsLoc = env["PROJECT_DIR"] + "/version.properties" prefsLoc = env["PROJECT_DIR"] + "/version.properties"
verObj = readProps(prefsLoc) verObj = readProps(prefsLoc)
env.Replace(PROGNAME=f"firmware-{env.get('PIOENV')}-{verObj['long']}") env.Replace(PROGNAME=f"firmware-{env.get('PIOENV')}-{verObj['long']}")
env.Replace(ESP32_FS_IMAGE_NAME=f"littlefs-{env.get('PIOENV')}-{verObj['long']}")
# Print the new program name for verification # Print the new program name for verification
print(f"PROGNAME: {env.get('PROGNAME')}") print(f"PROGNAME: {env.get('PROGNAME')}")
if platform.name == "espressif32":
print(f"ESP32_FS_IMAGE_NAME: {env.get('ESP32_FS_IMAGE_NAME')}")

View File

@@ -1659,6 +1659,25 @@ int Screen::handleInputEvent(const InputEvent *event)
} }
} }
} }
// UP/DOWN in node list screens scrolls through node pages
if (ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_nodes ||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_location ||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard ||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance ||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings) {
if (event->inputEvent == INPUT_BROKER_UP) {
graphics::NodeListRenderer::scrollUp();
setFastFramerate();
return 0;
}
if (event->inputEvent == INPUT_BROKER_DOWN) {
graphics::NodeListRenderer::scrollDown();
setFastFramerate();
return 0;
}
}
// Use left or right input from a keyboard to move between frames, // Use left or right input from a keyboard to move between frames,
// so long as a mesh module isn't using these events for some other purpose // so long as a mesh module isn't using these events for some other purpose
if (showingNormalScreen) { if (showingNormalScreen) {

View File

@@ -1126,10 +1126,8 @@ void menuHandler::nodeListMenu()
optionsEnumArray[options++] = Verify; optionsEnumArray[options++] = Verify;
#endif #endif
#if defined(T_DECK) || defined(T_LORA_PAGER) || defined(HACKADAY_COMMUNICATOR)
optionsArray[options] = "Show Long/Short Name"; optionsArray[options] = "Show Long/Short Name";
optionsEnumArray[options++] = NodeNameLength; optionsEnumArray[options++] = NodeNameLength;
#endif
optionsArray[options] = "Reset NodeDB"; optionsArray[options] = "Reset NodeDB";
optionsEnumArray[options++] = Reset; optionsEnumArray[options++] = Reset;
@@ -1177,7 +1175,7 @@ void menuHandler::nodeNameLengthMenu()
LOG_INFO("Setting names to short"); LOG_INFO("Setting names to short");
config.display.use_long_node_name = false; config.display.use_long_node_name = false;
} else if (selected == Back) { } else if (selected == Back) {
menuQueue = screen_options_menu; menuQueue = node_base_menu;
screen->runNow(); screen->runNow();
} }
}; };
@@ -1205,6 +1203,9 @@ void menuHandler::resetNodeDBMenu()
LOG_INFO("Initiate node-db reset but keeping favorites"); LOG_INFO("Initiate node-db reset but keeping favorites");
nodeDB->resetNodes(1); nodeDB->resetNodes(1);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
} else if (selected == 0) {
menuQueue = node_base_menu;
screen->runNow();
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@@ -2099,6 +2100,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case position_base_menu: case position_base_menu:
positionBaseMenu(); positionBaseMenu();
break; break;
case node_base_menu:
nodeListMenu();
break;
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
case gps_toggle_menu: case gps_toggle_menu:
GPSToggleMenu(); GPSToggleMenu();

View File

@@ -19,6 +19,7 @@ class menuHandler
clock_face_picker, clock_face_picker,
clock_menu, clock_menu,
position_base_menu, position_base_menu,
node_base_menu,
gps_toggle_menu, gps_toggle_menu,
gps_format_menu, gps_format_menu,
compass_point_north_menu, compass_point_north_menu,

View File

@@ -49,12 +49,57 @@ void drawScaledXBitmap16x16(int x, int y, int width, int height, const uint8_t *
static ListMode_Node currentMode_Nodes = MODE_LAST_HEARD; static ListMode_Node currentMode_Nodes = MODE_LAST_HEARD;
static ListMode_Location currentMode_Location = MODE_DISTANCE; static ListMode_Location currentMode_Location = MODE_DISTANCE;
static int scrollIndex = 0; static int scrollIndex = 0;
// Popup overlay state
static uint32_t popupTime = 0;
static int popupTotal = 0;
static int popupStart = 0;
static int popupEnd = 0;
static int popupPage = 1;
static int popupMaxPage = 1;
static const uint32_t POPUP_DURATION_MS = 1000; // 1 second visible
// =============================
// Scrolling Logic
// =============================
void scrollUp()
{
if (scrollIndex > 0)
scrollIndex--;
popupTime = millis(); // show popup
}
void scrollDown()
{
int totalEntries = nodeDB->getNumMeshNodes();
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
int screenHeight = screen->getHeight();
int visibleRows = (screenHeight - COMMON_HEADER_HEIGHT) / rowYOffset;
int totalColumns = 2;
#if defined(T_LORA_PAGER)
totalColumns = 3;
#endif
if (config.display.use_long_node_name)
totalColumns = 1;
int maxScroll = std::max(0, (totalEntries - 1) / (visibleRows * totalColumns));
if (scrollIndex < maxScroll)
scrollIndex++;
popupTime = millis();
}
// ============================= // =============================
// Utility Functions // Utility Functions
// ============================= // =============================
const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node) const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int columnWidth)
{ {
static char nodeName[25]; // single static buffer we return static char nodeName[25]; // single static buffer we return
nodeName[0] = '\0'; nodeName[0] = '\0';
@@ -82,7 +127,7 @@ const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node)
// 4) Width-based truncation + ellipsis (long-name mode only) // 4) Width-based truncation + ellipsis (long-name mode only)
if (config.display.use_long_node_name && display) { if (config.display.use_long_node_name && display) {
int availWidth = (SCREEN_WIDTH / 2) - 65; int availWidth = columnWidth - (isHighResolution ? 65 : 38);
if (availWidth < 0) if (availWidth < 0)
availWidth = 0; availWidth = 0;
@@ -181,7 +226,7 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
bool isLeftCol = (x < SCREEN_WIDTH / 2); bool isLeftCol = (x < SCREEN_WIDTH / 2);
int timeOffset = (isHighResolution) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7); int timeOffset = (isHighResolution) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
const char *nodeName = getSafeNodeName(display, node); const char *nodeName = getSafeNodeName(display, node, columnWidth);
char timeStr[10]; char timeStr[10];
uint32_t seconds = sinceLastSeen(node); uint32_t seconds = sinceLastSeen(node);
@@ -226,7 +271,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
int barsXOffset = columnWidth - barsOffset; int barsXOffset = columnWidth - barsOffset;
const char *nodeName = getSafeNodeName(display, node); const char *nodeName = getSafeNodeName(display, node, columnWidth);
display->setTextAlignment(TEXT_ALIGN_LEFT); display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
@@ -270,7 +315,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
bool isLeftCol = (x < SCREEN_WIDTH / 2); bool isLeftCol = (x < SCREEN_WIDTH / 2);
int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22)); int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
const char *nodeName = getSafeNodeName(display, node); const char *nodeName = getSafeNodeName(display, node, columnWidth);
char distStr[10] = ""; char distStr[10] = "";
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
@@ -362,7 +407,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
// Adjust max text width depending on column and screen width // Adjust max text width depending on column and screen width
int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22)); int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
const char *nodeName = getSafeNodeName(display, node); const char *nodeName = getSafeNodeName(display, node, columnWidth);
display->setTextAlignment(TEXT_ALIGN_LEFT); display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
@@ -440,17 +485,6 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
locationScreen = true; locationScreen = true;
else if (strcmp(title, "Distance") == 0) else if (strcmp(title, "Distance") == 0)
locationScreen = true; locationScreen = true;
#if defined(M5STACK_UNITC6L)
int columnWidth = display->getWidth();
#elif defined(T_LORA_PAGER)
int columnWidth = display->getWidth() / 3;
if (config.display.use_long_node_name) {
columnWidth = display->getWidth() / 2;
}
#else
int columnWidth = display->getWidth() / 2;
#endif
display->clear(); display->clear();
// Draw the battery/time header // Draw the battery/time header
@@ -459,44 +493,65 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
// Space below header // Space below header
y += COMMON_HEADER_HEIGHT; y += COMMON_HEADER_HEIGHT;
int totalColumns = 1; // Default to 1 column
if (config.display.use_long_node_name) {
if (SCREEN_WIDTH <= 240) {
totalColumns = 1;
} else if (SCREEN_WIDTH > 240) {
totalColumns = 2;
}
} else {
if (SCREEN_WIDTH <= 64) {
totalColumns = 1;
} else if (SCREEN_WIDTH > 64 && SCREEN_WIDTH <= 240) {
totalColumns = 2;
} else {
totalColumns = 3;
}
}
int columnWidth = display->getWidth() / totalColumns;
int totalEntries = nodeDB->getNumMeshNodes(); int totalEntries = nodeDB->getNumMeshNodes();
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset; int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
int numskipped = 0; int numskipped = 0;
int visibleNodeRows = totalRowsAvailable; int visibleNodeRows = totalRowsAvailable;
#if defined(M5STACK_UNITC6L)
int totalColumns = 1;
#elif defined(T_LORA_PAGER)
int totalColumns = 3;
if (config.display.use_long_node_name) {
totalColumns = 2;
}
#else
int totalColumns = 2;
#endif
int startIndex = scrollIndex * visibleNodeRows * totalColumns;
if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) {
startIndex++; // skip own node
}
int endIndex = std::min(startIndex + visibleNodeRows * totalColumns, totalEntries);
// Build filtered + ordered list
std::vector<int> drawList;
drawList.reserve(totalEntries);
for (int i = 0; i < totalEntries; i++) {
auto *n = nodeDB->getMeshNodeByIndex(i);
if (!n)
continue;
if (n->num == nodeDB->getNodeNum())
continue;
if (locationScreen && !n->has_position)
continue;
drawList.push_back(n->num);
}
totalEntries = drawList.size();
int startIndex = scrollIndex * visibleNodeRows * totalColumns;
int endIndex = std::min(startIndex + visibleNodeRows * totalColumns, totalEntries);
int yOffset = 0; int yOffset = 0;
int col = 0; int col = 0;
int lastNodeY = y; int lastNodeY = y;
int shownCount = 0; int shownCount = 0;
int rowCount = 0; int rowCount = 0;
for (int i = startIndex; i < endIndex; ++i) { for (int idx = startIndex; idx < endIndex; idx++) {
if (locationScreen && !nodeDB->getMeshNodeByIndex(i)->has_position) { uint32_t nodeNum = drawList[idx];
numskipped++; auto *node = nodeDB->getMeshNode(nodeNum);
continue;
}
int xPos = x + (col * columnWidth); int xPos = x + (col * columnWidth);
int yPos = y + yOffset; int yPos = y + yOffset;
renderer(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth);
if (extras) { renderer(display, node, xPos, yPos, columnWidth);
extras(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth, heading, lat, lon);
} if (extras)
extras(display, node, xPos, yPos, columnWidth, heading, lat, lon);
lastNodeY = std::max(lastNodeY, yPos + FONT_HEIGHT_SMALL); lastNodeY = std::max(lastNodeY, yPos + FONT_HEIGHT_SMALL);
yOffset += rowYOffset; yOffset += rowYOffset;
@@ -528,6 +583,62 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
const int scrollStartY = y + 3; const int scrollStartY = y + 3;
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY); drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
graphics::drawCommonFooter(display, x, y); graphics::drawCommonFooter(display, x, y);
// Scroll Popup Overlay
if (millis() - popupTime < POPUP_DURATION_MS) {
popupTotal = totalEntries;
int perPage = visibleNodeRows * totalColumns;
popupStart = startIndex + 1;
popupEnd = std::min(startIndex + perPage, totalEntries);
popupPage = (scrollIndex + 1);
popupMaxPage = std::max(1, (totalEntries + perPage - 1) / perPage);
char buf[32];
snprintf(buf, sizeof(buf), "%d-%d/%d Pg %d/%d", popupStart, popupEnd, popupTotal, popupPage, popupMaxPage);
display->setTextAlignment(TEXT_ALIGN_LEFT);
// Box padding
int padding = 2;
int textW = display->getStringWidth(buf);
int textH = FONT_HEIGHT_SMALL;
int boxWidth = textW + padding * 3;
int boxHeight = textH + padding * 2;
// Center of usable screen area:
int headerHeight = FONT_HEIGHT_SMALL - 1;
int footerHeight = FONT_HEIGHT_SMALL + 2;
int usableTop = headerHeight;
int usableBottom = display->getHeight() - footerHeight;
int usableHeight = usableBottom - usableTop;
// Center point inside usable area
int boxLeft = (display->getWidth() - boxWidth) / 2;
int boxTop = usableTop + (usableHeight - boxHeight) / 2;
// Draw Box
display->setColor(BLACK);
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2);
display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
display->fillRect(boxLeft, boxTop + boxHeight + 1, boxWidth, 1);
display->fillRect(boxLeft - 2, boxTop, 1, boxHeight);
display->fillRect(boxLeft + boxWidth + 1, boxTop, 1, boxHeight);
display->setColor(WHITE);
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
display->setColor(BLACK);
display->fillRect(boxLeft, boxTop, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1);
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
display->setColor(WHITE);
// Text
display->drawString(boxLeft + padding, boxTop + padding, buf);
}
} }
// ============================= // =============================

View File

@@ -56,9 +56,13 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
// Utility functions // Utility functions
const char *getCurrentModeTitle_Nodes(int screenWidth); const char *getCurrentModeTitle_Nodes(int screenWidth);
const char *getCurrentModeTitle_Location(int screenWidth); const char *getCurrentModeTitle_Location(int screenWidth);
const char *getSafeNodeName(meshtastic_NodeInfoLite *node); const char *getSafeNodeName(meshtastic_NodeInfoLite *node, int columnWidth);
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields); void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields);
// Scrolling controls
void scrollUp();
void scrollDown();
// Bitmap drawing function // Bitmap drawing function
void drawScaledXBitmap16x16(int x, int y, int width, int height, const uint8_t *bitmapXBM, OLEDDisplay *display); void drawScaledXBitmap16x16(int x, int y, int width, int height, const uint8_t *bitmapXBM, OLEDDisplay *display);