Full T-echo card support + Compact UI (#11342)

* T-echo card

* Update NRF52I2SOutput.cpp

* Update NRF52I2SOutput.h

* cleanup

* Update buzz.cpp

* use consistent runtime compact-panel check instead of mixing with compile-time macro

* Update NodeDB.cpp

* Update ExternalNotificationModule.cpp

* switched to Throttle::isWithinTimespanMs

* Update SharedUIDisplay.h

* trunk fix

* last cleanup

* ClockRenderer.cpp for OLED_COMPACT_UI and setup Unit C6L for new UI.

* Fixed regressions in standard OLED and TFT

---------

Co-authored-by: Jason P <applewiz@mac.com>
This commit is contained in:
HarukiToreda
2026-08-04 14:27:35 +00:00
committed by GitHub
co-authored by Jason P
parent d89f3eb8fc
commit 39e7aa6a6c
26 changed files with 1231 additions and 186 deletions
+91 -38
View File
@@ -69,6 +69,12 @@ void scrollDown()
if (maxScroll < 0)
maxScroll = 0;
if (graphics::isCompactPanel(screen->getDisplayDevice()) && scrollY >= maxScroll) {
// Compact panels: scrolling past the bottom wraps back to the top.
scrollY = 0;
return;
}
scrollY += 12;
if (scrollY > maxScroll)
scrollY = maxScroll;
@@ -427,9 +433,12 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
const int navHeight = FONT_HEIGHT_SMALL;
const bool compactPanel = graphics::isCompactPanel(display);
// Compact panels: no bottom nav row anymore (see UIRenderer::drawNavigationBar), full height available.
const int navHeight = compactPanel ? 0 : FONT_HEIGHT_SMALL;
const int scrollBottom = SCREEN_HEIGHT - navHeight;
const int usableHeight = scrollBottom;
const int contentTop = compactPanel ? 0 : getTextPositions(display)[1];
const int usableHeight = compactPanel ? scrollBottom - contentTop : scrollBottom;
constexpr int LEFT_MARGIN = 2;
constexpr int RIGHT_MARGIN = 2;
constexpr int SCROLLBAR_WIDTH = 3;
@@ -440,7 +449,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
constexpr int BUBBLE_TEXT_INDENT = 2;
// Check if bubbles are enabled
const bool showBubbles = config.display.enable_message_bubbles;
const bool showBubbles = config.display.enable_message_bubbles && !compactPanel;
const int textIndent = showBubbles ? (BUBBLE_PAD_X + BUBBLE_TEXT_INDENT) : LEFT_MARGIN;
// Derived widths
@@ -616,13 +625,17 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
snprintf(senderName, sizeof(senderName), "(%08x)", m.dest);
}
// Shrink Sender name if needed
int availWidth = (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) -
display->getStringWidth(chanType) - graphics::UIRenderer::measureStringWithEmotes(display, " *@...");
// Shrink Sender name if needed; compact panels put it on its own line, so no sharing with timeBuf/chanType.
int availWidth = compactPanel ? (mine ? rightTextWidth : leftTextWidth)
: (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) -
display->getStringWidth(chanType);
// Compact panels hard-cut (no "...") so drop its width reservation too.
availWidth -= graphics::UIRenderer::measureStringWithEmotes(display, compactPanel ? "*@" : " *@...");
if (availWidth < 0)
availWidth = 0;
char truncatedSender[64];
graphics::UIRenderer::truncateStringWithEmotes(display, senderName, truncatedSender, sizeof(truncatedSender), availWidth);
graphics::UIRenderer::truncateStringWithEmotes(display, senderName, truncatedSender, sizeof(truncatedSender), availWidth,
compactPanel ? "" : "...");
// Determine signed-message prefix before building the header line, since it needs to go
// at the front of headerStr rather than appended after (strncat only appends at the end).
@@ -634,28 +647,58 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
}
#endif
// Final header line
char headerStr[128];
if (mine) {
if (currentMode == ThreadMode::ALL) {
if (strcmp(chanType, "(DM)") == 0) {
snprintf(headerStr, sizeof(headerStr), "%s to %s", timeBuf, truncatedSender);
} else {
snprintf(headerStr, sizeof(headerStr), "%s to %s", timeBuf, chanType);
if (compactPanel) {
// Time and sender don't fit on one line at this width - time first, name below.
allLines.push_back(timeBuf);
isMine.push_back(mine);
isHeader.push_back(true);
ackForLine.push_back(AckStatus::NONE); // ack mark shown on the name line instead
char nameLine[80] = "";
if (mine) {
if (currentMode == ThreadMode::ALL) {
if (strcmp(chanType, "(DM)") == 0) {
snprintf(nameLine, sizeof(nameLine), "to %s", truncatedSender);
} else {
snprintf(nameLine, sizeof(nameLine), "to %s", chanType);
}
}
} else {
snprintf(headerStr, sizeof(headerStr), "%s", timeBuf);
snprintf(nameLine, sizeof(nameLine), chanType[0] ? "%s%s@%s" : "%s%s", signPrefix, truncatedSender, chanType);
}
if (nameLine[0]) {
allLines.push_back(nameLine);
isMine.push_back(mine);
isHeader.push_back(true);
ackForLine.push_back(m.ackStatus);
} else {
// Nothing to show on a second line (e.g. "mine" in ALL mode) - move the ack mark back.
ackForLine.back() = m.ackStatus;
}
} else {
snprintf(headerStr, sizeof(headerStr), chanType[0] ? "%s %s@%s %s" : "%s %s@%s", timeBuf, signPrefix, truncatedSender,
chanType);
}
// Final header line
char headerStr[128];
if (mine) {
if (currentMode == ThreadMode::ALL) {
if (strcmp(chanType, "(DM)") == 0) {
snprintf(headerStr, sizeof(headerStr), "%s to %s", timeBuf, truncatedSender);
} else {
snprintf(headerStr, sizeof(headerStr), "%s to %s", timeBuf, chanType);
}
} else {
snprintf(headerStr, sizeof(headerStr), "%s", timeBuf);
}
} else {
snprintf(headerStr, sizeof(headerStr), chanType[0] ? "%s %s@%s %s" : "%s %s@%s", timeBuf, signPrefix,
truncatedSender, chanType);
}
// Push header line
allLines.push_back(headerStr);
isMine.push_back(mine);
isHeader.push_back(true);
ackForLine.push_back(m.ackStatus);
allLines.push_back(headerStr);
isMine.push_back(mine);
isHeader.push_back(true);
ackForLine.push_back(m.ackStatus);
}
const char *msgText = MessageStore::getText(m);
@@ -679,6 +722,13 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Cache lines and heights
cachedLines.swap(allLines);
cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader);
if (compactPanel) {
for (size_t i = 0; i < cachedHeights.size(); ++i) {
if (isHeader[i]) {
cachedHeights[i] = 10;
}
}
}
std::vector<MessageBlock> blocks = buildMessageBlocks(isHeader, isMine);
@@ -728,8 +778,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
#endif
int finalScroll = (int)scrollY;
int yOffset = -finalScroll + getTextPositions(display)[1];
const int contentTop = getTextPositions(display)[1];
int yOffset = -finalScroll + contentTop;
const int contentBottom = scrollBottom; // already excludes nav line
const int rightEdge = SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN;
const int bubbleGapY = std::max(1, MESSAGE_BLOCK_GAP / 2);
@@ -907,18 +956,20 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
graphics::UIRenderer::drawStringWithEmotes(display, headerX, lineY, cachedLines[i].c_str(), FONT_HEIGHT_SMALL, 1,
true);
// Draw underline just under header text
int underlineY = lineY + FONT_HEIGHT_SMALL;
if (!compactPanel) {
// Draw underline just under header text
int underlineY = lineY + FONT_HEIGHT_SMALL;
int underlineW = w;
int maxW = rightEdge - headerX;
if (maxW < 0)
maxW = 0;
if (underlineW > maxW)
underlineW = maxW;
int underlineW = w;
int maxW = rightEdge - headerX;
if (maxW < 0)
maxW = 0;
if (underlineW > maxW)
underlineW = maxW;
for (int px = 0; px < underlineW; ++px) {
display->setPixel(headerX + px, underlineY);
for (int px = 0; px < underlineW; ++px) {
display->setPixel(headerX + px, underlineY);
}
}
// Draw ACK/NACK mark for our own messages
@@ -958,8 +1009,10 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
}
// Draw scrollbar
drawMessageScrollbar(display, usableHeight, totalHeight, finalScroll, getTextPositions(display)[1]);
graphics::drawCommonHeader(display, x, y, titleStr);
drawMessageScrollbar(display, usableHeight, totalHeight, finalScroll, contentTop);
if (!compactPanel) {
graphics::drawCommonHeader(display, x, y, titleStr);
}
graphics::drawCommonFooter(display, x, y);
}