mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-13 22:32:27 +00:00
Compare commits
7 Commits
eeb5d0478e
...
2dc54cdd4b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dc54cdd4b | ||
|
|
1b7104b1e2 | ||
|
|
ef99939d6f | ||
|
|
4abd3f9a1f | ||
|
|
cfea55d77d | ||
|
|
bcfe069997 | ||
|
|
71bc99938c |
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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')}")
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================
|
// =============================
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user