BaseUI: Emote Refactoring (#9896)

* Emote refactor for BaseUI

* Trunk Check

* Copilot suggestions
This commit is contained in:
HarukiToreda
2026-03-17 20:42:37 -05:00
committed by GitHub
co-authored by GitHub
parent 19d070c284
commit 2ef09d17b9
10 changed files with 940 additions and 623 deletions
+47 -54
View File
@@ -79,13 +79,15 @@ void scrollDown()
// Utility Functions
// =============================
const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int columnWidth)
std::string getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int columnWidth)
{
static char nodeName[25]; // single static buffer we return
nodeName[0] = '\0';
(void)display;
(void)columnWidth;
auto writeFallbackId = [&] {
std::snprintf(nodeName, sizeof(nodeName), "(%04X)", static_cast<uint16_t>(node ? (node->num & 0xFFFF) : 0));
auto fallbackId = [&] {
char id[12];
std::snprintf(id, sizeof(id), "(%04X)", static_cast<uint16_t>(node ? (node->num & 0xFFFF) : 0));
return std::string(id);
};
// 1) Choose target candidate (long vs short) only if present
@@ -94,42 +96,10 @@ const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node,
raw = config.display.use_long_node_name ? node->user.long_name : node->user.short_name;
}
// 2) Sanitize (empty if raw is null/empty)
std::string s = (raw && *raw) ? sanitizeString(raw) : std::string{};
// 3) Fallback if sanitize yields empty; otherwise copy safely (truncate if needed)
if (s.empty() || s == "¿" || s.find_first_not_of("¿") == std::string::npos) {
writeFallbackId();
} else {
// %.*s ensures null-termination and safe truncation to buffer size - 1
std::snprintf(nodeName, sizeof(nodeName), "%.*s", static_cast<int>(sizeof(nodeName) - 1), s.c_str());
}
// 4) Width-based truncation + ellipsis (long-name mode only)
if (config.display.use_long_node_name && display) {
int availWidth = columnWidth - ((currentResolution == ScreenResolution::High) ? 65 : 38);
if (availWidth < 0)
availWidth = 0;
const size_t beforeLen = std::strlen(nodeName);
// Trim from the end until it fits or is empty
size_t len = beforeLen;
while (len && display->getStringWidth(nodeName) > availWidth) {
nodeName[--len] = '\0';
}
// If truncated, append "..." (respect buffer size)
if (len < beforeLen) {
// Make sure there's room for "..." and '\0'
const size_t capForText = sizeof(nodeName) - 1; // leaving space for '\0'
const size_t needed = 3; // "..."
if (len > capForText - needed) {
len = capForText - needed;
nodeName[len] = '\0';
}
std::strcat(nodeName, "...");
}
// 2) Preserve UTF-8 names so emotes can be detected and rendered.
std::string nodeName = (raw && *raw) ? std::string(raw) : std::string{};
if (nodeName.empty()) {
nodeName = fallbackId();
}
return nodeName;
@@ -163,6 +133,15 @@ const char *getCurrentModeTitle_Location(int screenWidth)
}
}
static int getNodeNameMaxWidth(int columnWidth, int baseWidth)
{
if (!config.display.use_long_node_name)
return baseWidth;
const int legacyLongNameWidth = columnWidth - ((currentResolution == ScreenResolution::High) ? 65 : 38);
return std::max(0, std::min(baseWidth, legacyLongNameWidth));
}
// Use dynamic timing based on mode
unsigned long getModeCycleIntervalMs()
{
@@ -205,10 +184,13 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
{
bool isLeftCol = (x < SCREEN_WIDTH / 2);
int nameMaxWidth = columnWidth - 25;
int nameMaxWidth = getNodeNameMaxWidth(columnWidth, columnWidth - 25);
int timeOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
const char *nodeName = getSafeNodeName(display, node, columnWidth);
const int nameX = x + ((currentResolution == ScreenResolution::High) ? 6 : 3);
char nodeName[96];
UIRenderer::truncateStringWithEmotes(display, getSafeNodeName(display, node, columnWidth).c_str(), nodeName, sizeof(nodeName),
nameMaxWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
char timeStr[10];
@@ -228,7 +210,7 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
display->drawString(x + ((currentResolution == ScreenResolution::High) ? 6 : 3), y, nodeName);
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
if (node->is_favorite) {
if (currentResolution == ScreenResolution::High) {
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
@@ -255,19 +237,22 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
{
bool isLeftCol = (x < SCREEN_WIDTH / 2);
int nameMaxWidth = columnWidth - 25;
int nameMaxWidth = getNodeNameMaxWidth(columnWidth, columnWidth - 25);
int barsOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 20 : 24) : (isLeftCol ? 15 : 19);
int hopOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 21 : 29) : (isLeftCol ? 13 : 17);
int barsXOffset = columnWidth - barsOffset;
const char *nodeName = getSafeNodeName(display, node, columnWidth);
const int nameX = x + ((currentResolution == ScreenResolution::High) ? 6 : 3);
char nodeName[96];
UIRenderer::truncateStringWithEmotes(display, getSafeNodeName(display, node, columnWidth).c_str(), nodeName, sizeof(nodeName),
nameMaxWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
display->drawStringMaxWidth(x + ((currentResolution == ScreenResolution::High) ? 6 : 3), y, nameMaxWidth, nodeName);
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
if (node->is_favorite) {
if (currentResolution == ScreenResolution::High) {
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
@@ -312,9 +297,13 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
{
bool isLeftCol = (x < SCREEN_WIDTH / 2);
int nameMaxWidth =
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
getNodeNameMaxWidth(columnWidth, columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28)
: (isLeftCol ? 20 : 22)));
const char *nodeName = getSafeNodeName(display, node, columnWidth);
const int nameX = x + ((currentResolution == ScreenResolution::High) ? 6 : 3);
char nodeName[96];
UIRenderer::truncateStringWithEmotes(display, getSafeNodeName(display, node, columnWidth).c_str(), nodeName, sizeof(nodeName),
nameMaxWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
char distStr[10] = "";
@@ -368,7 +357,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
display->drawStringMaxWidth(x + ((currentResolution == ScreenResolution::High) ? 6 : 3), y, nameMaxWidth, nodeName);
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
if (node->is_favorite) {
if (currentResolution == ScreenResolution::High) {
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
@@ -414,14 +403,18 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
// Adjust max text width depending on column and screen width
int nameMaxWidth =
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
getNodeNameMaxWidth(columnWidth, columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28)
: (isLeftCol ? 20 : 22)));
const char *nodeName = getSafeNodeName(display, node, columnWidth);
const int nameX = x + ((currentResolution == ScreenResolution::High) ? 6 : 3);
char nodeName[96];
UIRenderer::truncateStringWithEmotes(display, getSafeNodeName(display, node, columnWidth).c_str(), nodeName, sizeof(nodeName),
nameMaxWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
display->drawStringMaxWidth(x + ((currentResolution == ScreenResolution::High) ? 6 : 3), y, nameMaxWidth, nodeName);
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
if (node->is_favorite) {
if (currentResolution == ScreenResolution::High) {
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
@@ -828,4 +821,4 @@ void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields
} // namespace NodeListRenderer
} // namespace graphics
#endif
#endif