t5s3 ui, screen refresh and ghosting fixes

This commit is contained in:
pelgraine
2026-03-12 16:15:16 +11:00
parent 467773366b
commit 3372c4aa1d
5 changed files with 68 additions and 45 deletions
+11 -7
View File
@@ -519,7 +519,7 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
// Home screen FIRST page: tile taps (virtual coordinate hit test)
if (ui_task.isOnHomeScreen() && ui_task.isHomeShowingTiles()) {
const int tileW = 40, tileH = 32, gapX = 1, gapY = 2;
const int tileW = 40, tileH = 28, gapX = 1, gapY = 1;
const int gridW = tileW * 3 + gapX * 2;
const int gridX = (128 - gridW) / 2; // =3
int gridY = ui_task.getTileGridVY();
@@ -539,13 +539,14 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
if (row == 1 && col == 1) { ui_task.gotoNotesScreen(); return 0; }
if (row == 1 && col == 2) { ui_task.gotoDiscoveryScreen(); return 0; }
}
// Tap outside tiles — cycle home pages
return (char)KEY_NEXT;
// Tap outside tiles — left half backward, right half forward
return (vx < 64) ? (char)KEY_PREV : (char)KEY_NEXT;
}
// Home screen (non-tile pages): tap cycles pages
// Home screen (non-tile pages): left half taps backward, right half forward
if (ui_task.isOnHomeScreen()) {
return (char)KEY_NEXT;
int vx = (int)(x / 7.5f);
return (vx < 64) ? (char)KEY_PREV : (char)KEY_NEXT;
}
// Reader (reading mode): tap = next page
@@ -586,9 +587,12 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
}
}
// Home screen: horizontal swipe cycles pages
// Home screen: swipe left = next page, swipe right = previous page
if (ui_task.isOnHomeScreen()) {
return (char)KEY_NEXT;
if (horizontal) {
return (dx < 0) ? (char)KEY_NEXT : (char)KEY_PREV;
}
return (char)KEY_NEXT; // vertical swipe = next (default)
}
// Settings: horizontal swipe → a/d for picker/number editing
@@ -854,7 +854,7 @@ private:
display.setColor(DisplayDriver::YELLOW);
display.setCursor(0, footerY);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Back:Cancel");
display.print("Boot:Cancel");
const char* right = "Tap:Confirm";
#else
display.print("Q:Cancel");
@@ -893,7 +893,7 @@ private:
display.setColor(DisplayDriver::YELLOW);
display.setCursor(0, footerY);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Back:Cancel");
display.print("Boot:Cancel");
const char* right = "Tap:Delete";
#else
display.print("Q:Cancel");
@@ -599,7 +599,7 @@ public:
switch (_state) {
case STATE_PASSWORD_ENTRY:
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Back:Exit");
display.print("Boot:Exit");
renderFooterRight(display, footerY, "Hold:Type");
#else
display.print("Sh+Del:Exit");
@@ -610,7 +610,7 @@ public:
case STATE_LOGGING_IN:
case STATE_COMMAND_PENDING:
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Back:Cancel");
display.print("Boot:Cancel");
#else
display.print("Sh+Del:Cancel");
#endif
@@ -618,7 +618,7 @@ public:
case STATE_CATEGORY_MENU:
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Back:Exit");
display.print("Boot:Exit");
renderFooterMidRight(display, footerY, "Back:Exit", "Tap:Open", "Swipe:Sel");
#else
display.print("Sh+Del:Exit");
@@ -628,7 +628,7 @@ public:
case STATE_COMMAND_MENU:
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Back:Back");
display.print("Boot:Back");
renderFooterMidRight(display, footerY, "Back:Back", "Tap:Run", "Swipe:Sel");
#else
display.print("Sh+Del:Back");
@@ -638,7 +638,7 @@ public:
case STATE_PARAM_ENTRY:
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Back:Cancel");
display.print("Boot:Cancel");
renderFooterRight(display, footerY, "Tap:Send");
#else
display.print("Sh+Del:Cancel");
@@ -648,7 +648,7 @@ public:
case STATE_CONFIRM:
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Back:No");
display.print("Boot:No");
renderFooterRight(display, footerY, "Tap:Yes");
#else
display.print("Sh+Del:No");
@@ -659,7 +659,7 @@ public:
case STATE_RESPONSE_VIEW:
case STATE_ERROR:
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Back:Back");
display.print("Boot:Back");
if (_responseTotalLines > bodyHeight / 9) {
renderFooterRight(display, footerY, "Swipe:Scroll");
}
@@ -1160,7 +1160,11 @@ private:
bool selected, const char* label, bool warn) {
if (selected) {
display.setColor(DisplayDriver::LIGHT);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.fillRect(0, y, display.width(), lineHeight);
#else
display.fillRect(0, y + 5, display.width(), lineHeight);
#endif
display.setColor(DisplayDriver::DARK);
} else if (warn) {
display.setColor(DisplayDriver::YELLOW);
+29 -23
View File
@@ -26,7 +26,11 @@
#define LONG_PRESS_MILLIS 1200
#ifndef UI_RECENT_LIST_SIZE
#define UI_RECENT_LIST_SIZE 4
#if defined(LilyGo_T5S3_EPaper_Pro)
#define UI_RECENT_LIST_SIZE 8
#else
#define UI_RECENT_LIST_SIZE 4
#endif
#endif
#if UI_HAS_JOYSTICK
@@ -145,9 +149,19 @@ void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts,
display.setColor(DisplayDriver::GREEN);
// battery icon dimensions (smaller to match tiny percentage text)
// battery icon dimensions
#if defined(LilyGo_T5S3_EPaper_Pro)
int iconWidth = 10; // Narrower — non-square scaling stretches width
#else
int iconWidth = 16;
#endif
int iconHeight = 6;
int iconY = 0;
#if defined(LilyGo_T5S3_EPaper_Pro)
int textY = 0; // T5S3: percentage beside icon, same baseline
#else
int textY = iconY - 3; // T-Deck Pro: offset up to center with icon
#endif
// measure percentage text width to position icon + text together at right edge
display.setTextSize(0);
@@ -155,16 +169,9 @@ void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts,
sprintf(pctStr, "%d%%", batteryPercentage);
uint16_t textWidth = display.getTextWidth(pctStr);
// layout: [icon 16px][cap 2px][gap 2px][text][margin 2px]
// layout: [icon][cap 2px][gap 2px][text][margin 2px]
int totalWidth = iconWidth + 2 + 2 + textWidth + 2;
int iconX = display.width() - totalWidth;
#if defined(LilyGo_T5S3_EPaper_Pro)
int iconY = 6; // Align with FreeSans12pt text baseline
int textY = 1; // Percentage text — setCursor adds +5 → baseline at (6)*scale_y
#else
int iconY = 0; // vertically align with node name text
int textY = iconY - 3; // offset up to vertically center with icon
#endif
if (outIconX) *outIconX = iconX;
@@ -178,8 +185,7 @@ void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts,
int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100;
display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4);
// draw percentage text after the battery cap, offset upward to center with icon
// (setCursor adds +5 internally for baseline, so compensate for the tiny font)
// draw percentage text after the battery cap
int textX = iconX + iconWidth + 2 + 2; // after cap + gap
display.setCursor(textX, textY);
display.print(pctStr);
@@ -306,7 +312,7 @@ public:
}
// curr page indicator
#if defined(LilyGo_T5S3_EPaper_Pro)
int y = 18;
int y = 14; // Closer to header
#else
int y = 14;
#endif
@@ -324,7 +330,7 @@ public:
_task->setHomeShowingTiles(true);
#endif
#if defined(LilyGo_T5S3_EPaper_Pro)
int y = 24;
int y = 18; // Tighter spacing
#else
int y = 20;
#endif
@@ -332,23 +338,23 @@ public:
display.setTextSize(2);
sprintf(tmp, "MSG: %d", _task->getUnreadMsgCount());
display.drawTextCentered(display.width() / 2, y, tmp);
y += 18;
y += 14; // Reduced from 18
#if defined(WIFI_SSID) || defined(MECK_WIFI_COMPANION)
IPAddress ip = WiFi.localIP();
if (ip != IPAddress(0,0,0,0)) {
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d:%d", ip[0], ip[1], ip[2], ip[3], TCP_PORT);
display.setTextSize(1);
display.setTextSize(0); // Tiny font for IP
display.drawTextCentered(display.width() / 2, y, tmp);
y += 12;
y += 8;
}
#endif
#if defined(BLE_PIN_CODE) || defined(WIFI_SSID) || defined(MECK_WIFI_COMPANION)
if (_task->hasConnection()) {
display.setColor(DisplayDriver::GREEN);
display.setTextSize(1);
display.setTextSize(0); // Tiny font for Connected
display.drawTextCentered(display.width() / 2, y, "< Connected >");
y += 12;
y += 8; // Reduced from 12
#ifdef BLE_PIN_CODE
} else if (_task->isSerialEnabled() && the_mesh.getBLEPin() != 0) {
display.setColor(DisplayDriver::RED);
@@ -372,9 +378,9 @@ public:
};
const int tileW = 40;
const int tileH = 32;
const int tileH = 28; // Reduced from 32 to fit with Connected text
const int gapX = 1;
const int gapY = 2;
const int gapY = 1; // Reduced from 2
const int gridW = tileW * 3 + gapX * 2;
const int gridX = (display.width() - gridW) / 2;
const int gridY = y + 2;
@@ -391,11 +397,11 @@ public:
// Letter centered in tile (pushed down for vertical centering)
display.setTextSize(2);
display.drawTextCentered(tx + tileW / 2, ty + 10, tiles[row][col].letter);
display.drawTextCentered(tx + tileW / 2, ty + 8, tiles[row][col].letter);
// Label centered below letter
display.setTextSize(0);
display.drawTextCentered(tx + tileW / 2, ty + 20, tiles[row][col].label);
display.drawTextCentered(tx + tileW / 2, ty + 18, tiles[row][col].label);
}
}
+15 -6
View File
@@ -22,7 +22,7 @@
// CLEAR_FAST, CLEAR_SLOW — full refresh modes
// Periodic slow (deep) refresh to clear ghosting
#define FULL_SLOW_PERIOD 20 // every 20 fast-refreshes, do a slow refresh
#define FULL_SLOW_PERIOD 2 // every 2 partial-refreshes, do a full cleanup
FastEPDDisplay::~FastEPDDisplay() {
delete _canvas;
@@ -282,7 +282,6 @@ void FastEPDDisplay::endFrame() {
// Copy GFXcanvas1 buffer to FastEPD's current buffer — direct copy.
// Both use same polarity: bit 1 = white, bit 0 = black.
// (Meshtastic inverts because OLEDDisplay uses opposite convention — not us.)
uint8_t* src = _canvas->getBuffer();
uint8_t* dst = _epd->currentBuffer();
size_t bufSize = ((uint32_t)EPD_WIDTH * EPD_HEIGHT) / 8;
@@ -291,9 +290,19 @@ void FastEPDDisplay::endFrame() {
memcpy(dst, src, bufSize);
// fullUpdate(true) is the only refresh mode that gives clean transitions
// on the ED047TC1 panel. CLEAR_FAST causes ghosting/mashing on this hardware.
// The brief white flash between frames is inherent to this panel's waveform.
_epd->fullUpdate(true);
// Refresh strategy:
// partialUpdate(true) — no flash, differential, keeps previous buffer
// fullUpdate(false) — brief flash, clears ghosting (CLEAR_FAST)
// fullUpdate(true) — full white flash, cleanest (boot only)
//
// Use partial for most frames. Periodic full refresh every N frames
// to clear accumulated ghosting artifacts.
_fullRefreshCount++;
if (_fullRefreshCount >= FULL_SLOW_PERIOD) {
_fullRefreshCount = 0;
_epd->fullUpdate(true); // Full clean refresh — clears all ghosting
} else {
_epd->partialUpdate(true); // No flash — differential
}
_epd->backupPlane();
}