Signal bars for message ack

This commit is contained in:
HarukiToreda 2025-10-10 10:50:15 -04:00
parent 84cef67b82
commit 784e71f2fa
2 changed files with 138 additions and 102 deletions

View File

@ -38,7 +38,7 @@ extern bool hasUnreadMessage;
namespace graphics namespace graphics
{ {
int bannerSignalBars = -1;
InputEvent NotificationRenderer::inEvent; InputEvent NotificationRenderer::inEvent;
int8_t NotificationRenderer::curSelected = 0; int8_t NotificationRenderer::curSelected = 0;
char NotificationRenderer::alertBannerMessage[256] = {0}; char NotificationRenderer::alertBannerMessage[256] = {0};
@ -461,10 +461,10 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[], void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[],
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth) uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth)
{ {
bool is_picker = false; bool is_picker = false;
uint16_t lineCount = 0; uint16_t lineCount = 0;
// === Layout Configuration ===
// Layout Configuration
constexpr uint16_t hPadding = 5; constexpr uint16_t hPadding = 5;
constexpr uint16_t vPadding = 2; constexpr uint16_t vPadding = 2;
bool needs_bell = false; bool needs_bell = false;
@ -478,13 +478,31 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT); display->setTextAlignment(TEXT_ALIGN_LEFT);
// Measure all lines and calculate widest width
uint16_t widestLineWithBars = 0;
while (lines[lineCount] != nullptr) { while (lines[lineCount] != nullptr) {
auto newlinePointer = strchr(lines[lineCount], '\n'); auto newlinePointer = strchr(lines[lineCount], '\n');
if (newlinePointer) if (newlinePointer)
lineLengths[lineCount] = (newlinePointer - lines[lineCount]); // Check for newlines first lineLengths[lineCount] = (newlinePointer - lines[lineCount]); // Check for newlines first
else // if the newline wasn't found, then pull string length from strlen else
lineLengths[lineCount] = strlen(lines[lineCount]); lineLengths[lineCount] = strlen(lines[lineCount]); // Fallback to strlen if no newline
lineWidths[lineCount] = display->getStringWidth(lines[lineCount], lineLengths[lineCount], true); lineWidths[lineCount] = display->getStringWidth(lines[lineCount], lineLengths[lineCount], true);
// Calculate potential width including signal bars (without altering per-line widths)
uint16_t potentialWidth = lineWidths[lineCount];
if (bannerSignalBars >= 0 && strstr(lines[lineCount], "Signal:") != nullptr) {
const int totalBars = 5;
const int barWidth = 3;
const int barSpacing = 2;
int barsWidth = totalBars * barWidth + (totalBars - 1) * barSpacing + 6;
potentialWidth += barsWidth;
}
if (potentialWidth > widestLineWithBars)
widestLineWithBars = potentialWidth;
if (!is_picker) { if (!is_picker) {
needs_bell |= (strstr(alertBannerMessage, "Alert Received") != nullptr); needs_bell |= (strstr(alertBannerMessage, "Alert Received") != nullptr);
if (lineWidths[lineCount] > maxWidth) if (lineWidths[lineCount] > maxWidth)
@ -492,10 +510,15 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
} }
lineCount++; lineCount++;
} }
// count lines
// Use widestLineWithBars to size the box if signal bars exist
if (widestLineWithBars > maxWidth)
maxWidth = widestLineWithBars;
uint16_t boxWidth = hPadding * 2 + maxWidth; uint16_t boxWidth = hPadding * 2 + maxWidth;
#if defined(M5STACK_UNITC6L) #if defined(M5STACK_UNITC6L)
// Special sizing rules for M5STACK_UNITC6L
if (needs_bell) { if (needs_bell) {
if (isHighResolution && boxWidth <= 150) if (isHighResolution && boxWidth <= 150)
boxWidth += 26; boxWidth += 26;
@ -527,7 +550,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
boxTop = 0; boxTop = 0;
} }
// === Draw Box === // Draw Box
display->setColor(BLACK); display->setColor(BLACK);
display->fillRect(boxLeft, boxTop, boxWidth, boxHeight); display->fillRect(boxLeft, boxTop, boxWidth, boxHeight);
display->setColor(WHITE); display->setColor(WHITE);
@ -541,89 +564,71 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1); display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1); display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
display->setColor(WHITE); display->setColor(WHITE);
int16_t lineY = boxTop + vPadding;
int swingRange = 8;
static int swingOffset = 0;
static bool swingRight = true;
static unsigned long lastSwingTime = 0;
unsigned long now = millis();
int swingSpeedMs = 10 / (swingRange * 2);
if (now - lastSwingTime >= (unsigned long)swingSpeedMs) {
lastSwingTime = now;
if (swingRight) {
swingOffset++;
if (swingOffset >= swingRange)
swingRight = false;
} else {
swingOffset--;
if (swingOffset <= 0)
swingRight = true;
}
}
for (int i = 0; i < lineCount; i++) {
bool isTitle = (i == 0);
int globalOptionIndex = (i - 1) + firstOptionToShow;
bool isSelectedOption = (!isTitle && globalOptionIndex >= 0 && globalOptionIndex == curSelected);
uint16_t visibleWidth = 64 - hPadding * 2; // Draw Content
if (totalLines > visibleTotalLines) int16_t lineY = boxTop + vPadding;
visibleWidth -= 6; for (int i = 0; i < lineCount; i++) {
char lineBuffer[lineLengths[i] + 1]; char lineBuffer[lineLengths[i] + 1];
strncpy(lineBuffer, lines[i], lineLengths[i]); strncpy(lineBuffer, lines[i], lineLengths[i]);
lineBuffer[lineLengths[i]] = '\0'; lineBuffer[lineLengths[i]] = '\0';
if (isTitle) { bool isSignalLine = (bannerSignalBars >= 0 && strstr(lineBuffer, "Signal:") != nullptr);
if (visibleTotalLines == 1) {
display->setColor(BLACK); if (isSignalLine) {
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight); // Center text + signal bars as a single group
display->setColor(WHITE); const int totalBars = 5;
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer); const int barWidth = 3;
} else { const int barSpacing = 2;
display->setColor(WHITE); const int barHeightStep = 2;
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
display->setColor(BLACK); int textWidth = display->getStringWidth(lineBuffer, strlen(lineBuffer), true);
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer); int barsWidth = totalBars * barWidth + (totalBars - 1) * barSpacing + 6;
display->setColor(WHITE); int totalWidth = textWidth + barsWidth;
if (needs_bell) {
int bellY = boxTop + (FONT_HEIGHT_SMALL - 8) / 2; int groupStartX = boxLeft + (boxWidth - totalWidth) / 2;
display->drawXbm(boxLeft + (boxWidth - lineWidths[i]) / 2 - 10, bellY, 8, 8, bell_alert);
display->drawXbm(boxLeft + (boxWidth + lineWidths[i]) / 2 + 2, bellY, 8, 8, bell_alert); display->drawString(groupStartX, lineY, lineBuffer);
int baseX = groupStartX + textWidth + 6;
int baseY = lineY + effectiveLineHeight - 1;
for (int b = 0; b < totalBars; b++) {
int barHeight = (b + 1) * barHeightStep;
int x = baseX + b * (barWidth + barSpacing);
int y = baseY - barHeight;
if (b < bannerSignalBars) {
display->fillRect(x, y, barWidth, barHeight);
} else {
display->drawRect(x, y, barWidth, barHeight);
} }
} }
lineY = boxTop + effectiveLineHeight + 1;
} else if (isSelectedOption) {
display->setColor(WHITE);
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
display->setColor(BLACK);
if (lineLengths[i] > 15 && lineWidths[i] > visibleWidth) {
int textX = boxLeft + hPadding + swingOffset;
display->drawString(textX, lineY - 1, lineBuffer);
} else {
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY - 1, lineBuffer);
}
display->setColor(WHITE);
lineY += effectiveLineHeight;
} else { } else {
display->setColor(BLACK); int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight); display->drawString(textX, lineY, lineBuffer);
display->setColor(WHITE);
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY, lineBuffer);
lineY += effectiveLineHeight;
} }
lineY += effectiveLineHeight;
} }
// Scroll Bar (Thicker, inside box, not over title)
if (totalLines > visibleTotalLines) { if (totalLines > visibleTotalLines) {
const uint8_t scrollBarWidth = 5; const uint8_t scrollBarWidth = 5;
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2; int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight; int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight; uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
float ratio = (float)visibleTotalLines / totalLines; float ratio = (float)visibleTotalLines / totalLines;
uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4); uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4);
float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines); float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines);
uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight); uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight);
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight); display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight); display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
} }
#else #else
// Default (non-M5STACK) layout
if (needs_bell) { if (needs_bell) {
if (isHighResolution && boxWidth <= 150) if (isHighResolution && boxWidth <= 150)
boxWidth += 26; boxWidth += 26;
@ -647,7 +652,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
int16_t boxTop = (display->height() / 2) - (boxHeight / 2); int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
boxHeight += (isHighResolution) ? 2 : 1; boxHeight += (isHighResolution) ? 2 : 1;
// === Draw Box === // Draw Box
display->setColor(BLACK); display->setColor(BLACK);
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2);
display->fillRect(boxLeft, boxTop - 2, boxWidth, 1); display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
@ -663,46 +668,57 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1); display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
display->setColor(WHITE); display->setColor(WHITE);
// === Draw Content === // Draw Content
int16_t lineY = boxTop + vPadding; int16_t lineY = boxTop + vPadding;
for (int i = 0; i < lineCount; i++) { for (int i = 0; i < lineCount; i++) {
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
if (needs_bell && i == 0) {
int bellY = lineY + (FONT_HEIGHT_SMALL - 8) / 2;
display->drawXbm(textX - 10, bellY, 8, 8, bell_alert);
display->drawXbm(textX + lineWidths[i] + 2, bellY, 8, 8, bell_alert);
}
char lineBuffer[lineLengths[i] + 1]; char lineBuffer[lineLengths[i] + 1];
strncpy(lineBuffer, lines[i], lineLengths[i]); strncpy(lineBuffer, lines[i], lineLengths[i]);
lineBuffer[lineLengths[i]] = '\0'; lineBuffer[lineLengths[i]] = '\0';
// Determine if this is a pop-up or a pick list
if (alertBannerOptions > 0 && i == 0) { bool isSignalLine = (bannerSignalBars >= 0 && strstr(lineBuffer, "Signal:") != nullptr);
// Pick List
display->setColor(WHITE); if (isSignalLine) {
int background_yOffset = 1; // Center text + signal bars as a single group
// Determine if we have low hanging characters const int totalBars = 5;
if (strchr(lineBuffer, 'p') || strchr(lineBuffer, 'g') || strchr(lineBuffer, 'y') || strchr(lineBuffer, 'j')) { const int barWidth = 3;
background_yOffset = -1; const int barSpacing = 2;
const int barHeightStep = 2;
int textWidth = display->getStringWidth(lineBuffer, strlen(lineBuffer), true);
int barsWidth = totalBars * barWidth + (totalBars - 1) * barSpacing + 6;
int totalWidth = textWidth + barsWidth;
int groupStartX = boxLeft + (boxWidth - totalWidth) / 2;
display->drawString(groupStartX, lineY, lineBuffer);
int baseX = groupStartX + textWidth + 6;
int baseY = lineY + effectiveLineHeight - 1;
for (int b = 0; b < totalBars; b++) {
int barHeight = (b + 1) * barHeightStep;
int x = baseX + b * (barWidth + barSpacing);
int y = baseY - barHeight;
if (b < bannerSignalBars) {
display->fillRect(x, y, barWidth, barHeight);
} else {
display->drawRect(x, y, barWidth, barHeight);
}
} }
display->fillRect(boxLeft, boxTop + 1, boxWidth, effectiveLineHeight - background_yOffset);
display->setColor(BLACK);
int yOffset = 3;
display->drawString(textX, lineY - yOffset, lineBuffer);
display->setColor(WHITE);
lineY += (effectiveLineHeight - 2 - background_yOffset);
} else { } else {
// Pop-up int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
display->drawString(textX, lineY, lineBuffer); display->drawString(textX, lineY, lineBuffer);
lineY += (effectiveLineHeight);
} }
lineY += effectiveLineHeight;
} }
// === Scroll Bar (Thicker, inside box, not over title) === // Scroll Bar (Thicker, inside box, not over title)
if (totalLines > visibleTotalLines) { if (totalLines > visibleTotalLines) {
const uint8_t scrollBarWidth = 5; const uint8_t scrollBarWidth = 5;
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2; int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight; // start after title line int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight; uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
float ratio = (float)visibleTotalLines / totalLines; float ratio = (float)visibleTotalLines / totalLines;

View File

@ -44,6 +44,10 @@ extern MessageStore messageStore;
// Remove Canned message screen if no action is taken for some milliseconds // Remove Canned message screen if no action is taken for some milliseconds
#define INACTIVATE_AFTER_MS 20000 #define INACTIVATE_AFTER_MS 20000
namespace graphics
{
extern int bannerSignalBars;
}
extern ScanI2C::DeviceAddress cardkb_found; extern ScanI2C::DeviceAddress cardkb_found;
extern bool graphics::isMuted; extern bool graphics::isMuted;
extern bool osk_found; extern bool osk_found;
@ -2167,7 +2171,7 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &
} else if (isAck) { } else if (isAck) {
// Relay ACK → mark as RELAYED, still no final ACK // Relay ACK → mark as RELAYED, still no final ACK
this->ack = false; this->ack = false;
waitingForAck = false; // dont wait for more waitingForAck = false;
} else { } else {
// Explicit failure // Explicit failure
this->ack = false; this->ack = false;
@ -2202,18 +2206,33 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &
const char *channelName = channels.getName(this->channel); const char *channelName = channels.getName(this->channel);
const char *nodeName = getNodeName(this->incoming); const char *nodeName = getNodeName(this->incoming);
// Signal quality calculation (Good / Fair / Bad)
const char *qualityLabel;
int bars = 0;
if (lastRxSnr > -7 && lastRxRssi > -110) {
qualityLabel = "Good";
bars = 4;
} else if (lastRxSnr > -15 && lastRxRssi > -120) {
qualityLabel = "Fair";
bars = 2;
} else {
qualityLabel = "Bad";
bars = 1;
}
if (this->ack) { if (this->ack) {
if (this->lastSentNode == NODENUM_BROADCAST) { if (this->lastSentNode == NODENUM_BROADCAST) {
snprintf(buf, sizeof(buf), "Message sent to\n#%s\n\nSNR: %.1f dB RSSI: %d dBm", snprintf(buf, sizeof(buf), "Message sent to\n#%s\n\nSignal: %s",
(channelName && channelName[0]) ? channelName : "unknown", this->lastRxSnr, this->lastRxRssi); (channelName && channelName[0]) ? channelName : "unknown", qualityLabel);
} else { } else {
snprintf(buf, sizeof(buf), "DM sent to\n@%s\n\nSNR: %.1f dB RSSI: %d dBm", snprintf(buf, sizeof(buf), "DM sent to\n@%s\n\nSignal: %s",
(nodeName && nodeName[0]) ? nodeName : "unknown", this->lastRxSnr, this->lastRxRssi); (nodeName && nodeName[0]) ? nodeName : "unknown", qualityLabel);
} }
} else if (isAck && !isFromDest) { } else if (isAck && !isFromDest) {
// Relay ACK banner // Relay ACK banner
snprintf(buf, sizeof(buf), "DM Relayed\n(Status Unknown)\n%s\n\nSNR: %.1f dB RSSI: %d dBm", snprintf(buf, sizeof(buf), "DM Relayed\n(Status Unknown)\n%s\n\nSignal: %s",
(nodeName && nodeName[0]) ? nodeName : "unknown", this->lastRxSnr, this->lastRxRssi); (nodeName && nodeName[0]) ? nodeName : "unknown", qualityLabel);
} else { } else {
if (this->lastSentNode == NODENUM_BROADCAST) { if (this->lastSentNode == NODENUM_BROADCAST) {
snprintf(buf, sizeof(buf), "Message failed to\n#%s", snprintf(buf, sizeof(buf), "Message failed to\n#%s",
@ -2225,7 +2244,8 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &
opts.message = buf; opts.message = buf;
opts.durationMs = 3000; opts.durationMs = 3000;
screen->showOverlayBanner(opts); graphics::bannerSignalBars = bars; // tell banner renderer how many bars to draw
screen->showOverlayBanner(opts); // this triggers drawNotificationBox()
} }
} }
} }