lower brightness to 4 for best darkroom reading; first prelim touch implementation; ui improvements

This commit is contained in:
pelgraine
2026-03-11 23:17:38 +11:00
parent 479673e90f
commit 2a0497e5ba
10 changed files with 532 additions and 28 deletions
+337 -2
View File
@@ -337,6 +337,58 @@
}
#endif
// =============================================================================
// T5S3 E-Paper Pro — GT911 Touch Input
// =============================================================================
#if defined(LilyGo_T5S3_EPaper_Pro)
#include "TouchDrvGT911.hpp"
#include <SD.h>
#include "TextReaderScreen.h"
#include "NotesScreen.h"
#include "ContactsScreen.h"
#include "ChannelScreen.h"
#include "SettingsScreen.h"
#include "RepeaterAdminScreen.h"
#include "DiscoveryScreen.h"
static TouchDrvGT911 gt911Touch;
static bool gt911Ready = false;
static bool sdCardReady = false; // T5S3 SD card state
// Touch state machine — supports tap, long press, and swipe
static bool touchDown = false;
static unsigned long touchDownTime = 0;
static int16_t touchDownX = 0;
static int16_t touchDownY = 0;
static int16_t touchLastX = 0;
static int16_t touchLastY = 0;
static unsigned long lastTouchSeenMs = 0; // Last time getPoint() returned true
#define TOUCH_LONG_PRESS_MS 500
#define TOUCH_SWIPE_THRESHOLD 60 // Min pixels to count as a swipe (physical)
#define TOUCH_LIFT_DEBOUNCE_MS 150 // No-touch duration before "finger lifted"
#define TOUCH_MIN_INTERVAL_MS 300 // Min ms between accepted events
static bool longPressHandled = false;
static bool swipeHandled = false;
static bool touchCooldown = false;
static unsigned long lastTouchEventMs = 0;
// Read GT911 in landscape orientation (960×540)
// GT911 reports portrait (540×960), rotate: x=raw_y, y=540-1-raw_x
// Note: Do NOT gate on GT911_PIN_INT — it pulses briefly per event
// and goes high between reports, causing drags to look like taps.
// Polling getPoint() directly works for continuous touch tracking.
static bool readTouchLandscape(int16_t* outX, int16_t* outY) {
if (!gt911Ready) return false;
int16_t raw_x, raw_y;
if (gt911Touch.getPoint(&raw_x, &raw_y)) {
*outX = raw_y;
*outY = EPD_HEIGHT - 1 - raw_x;
return true;
}
return false;
}
#endif
// Board-agnostic: CPU frequency scaling and AGC reset
CPUPowerManager cpuPower;
#define AGC_RESET_INTERVAL_MS 500
@@ -451,6 +503,122 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
/* END GLOBAL OBJECTS */
// T5S3 touch mapping — must be after ui_task declaration
#if defined(LilyGo_T5S3_EPaper_Pro)
// Home screen tile grid — physical touch zones (960×540)
#define TILE_Y_START 150
#define TILE_Y_MID 300
#define TILE_Y_END 460
#define TILE_COL1 325
#define TILE_COL2 640
// Map a single tap based on current screen context
static char mapTouchTap(int16_t x, int16_t y) {
// --- Status bar tap (top ~80px) → go home from any non-home screen ---
if (y < 80 && !ui_task.isOnHomeScreen()) {
ui_task.gotoHomeScreen();
return 0;
}
// Home screen FIRST page: tile taps
if (ui_task.isOnHomeScreen() && ui_task.isHomeShowingTiles()) {
if (y >= TILE_Y_START && y < TILE_Y_END) {
int col = (x < TILE_COL1) ? 0 : (x < TILE_COL2) ? 1 : 2;
int row = (y < TILE_Y_MID) ? 0 : 1;
if (row == 0 && col == 0) { ui_task.gotoChannelScreen(); return 0; }
if (row == 0 && col == 1) { ui_task.gotoContactsScreen(); return 0; }
if (row == 0 && col == 2) { ui_task.gotoSettingsScreen(); return 0; }
if (row == 1 && col == 0) { ui_task.gotoTextReader(); return 0; }
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;
}
// Home screen (non-tile pages): tap cycles pages
if (ui_task.isOnHomeScreen()) {
return (char)KEY_NEXT;
}
// Reader (reading mode): tap = next page
if (ui_task.isOnTextReader()) {
TextReaderScreen* reader = (TextReaderScreen*)ui_task.getTextReaderScreen();
if (reader && reader->isReading()) {
return 'd'; // next page
}
return KEY_ENTER; // file list: open selected
}
// All other screens: tap = select
return KEY_ENTER;
}
// Map a swipe direction to a key
static char mapTouchSwipe(int16_t dx, int16_t dy) {
bool horizontal = abs(dx) > abs(dy);
// Reader (reading mode): swipe left/right for page turn
if (ui_task.isOnTextReader()) {
TextReaderScreen* reader = (TextReaderScreen*)ui_task.getTextReaderScreen();
if (reader && reader->isReading()) {
if (horizontal) {
return (dx < 0) ? 'd' : 'a'; // swipe left=next, right=prev
}
// Vertical swipe in reader: also page turn (natural scroll)
return (dy > 0) ? 'd' : 'a'; // swipe down=next, up=prev
}
}
// Home screen: horizontal swipe cycles pages
if (ui_task.isOnHomeScreen()) {
return (char)KEY_NEXT;
}
// Settings: horizontal swipe → a/d for picker/number editing
if (ui_task.isOnSettingsScreen() && horizontal) {
return (dx < 0) ? 'd' : 'a'; // swipe left=next option, right=prev
}
// Channel screen: horizontal swipe → a/d to switch channels
if (ui_task.isOnChannelScreen() && horizontal) {
return (dx < 0) ? 'd' : 'a'; // swipe left=next channel, right=prev
}
// Contacts screen: horizontal swipe → a/d to change filter
if (ui_task.isOnContactsScreen() && horizontal) {
return (dx < 0) ? 'd' : 'a'; // swipe left=next filter, right=prev
}
// All other screens: vertical swipe scrolls
if (!horizontal) {
return (dy > 0) ? 's' : 'w'; // swipe down=scroll down, up=scroll up
}
return 0; // ignore horizontal swipes on non-applicable screens
}
// Map a long press to a key
static char mapTouchLongPress(int16_t x, int16_t y) {
// Home screen: long press cycles pages
if (ui_task.isOnHomeScreen()) {
return (char)KEY_NEXT;
}
// Reader reading: long press = close book
if (ui_task.isOnTextReader()) {
TextReaderScreen* reader = (TextReaderScreen*)ui_task.getTextReaderScreen();
if (reader && reader->isReading()) {
return 'q';
}
return KEY_ENTER; // file list: open
}
// Default: enter/select (settings toggle, etc.)
return KEY_ENTER;
}
#endif
void halt() {
while (1) ;
}
@@ -649,6 +817,37 @@ void setup() {
MESH_DEBUG_PRINTLN("setup() - SD card not available after 3 attempts");
}
}
#elif defined(LilyGo_T5S3_EPaper_Pro) && defined(HAS_SDCARD)
{
// T5S3: SD card shares LoRa SPI bus (SCK=14, MOSI=13, MISO=21)
// LoRa SPI already initialized by target.cpp. Create a local HSPI
// reference for SD init (same hardware peripheral, different CS).
static SPIClass sdSpi(HSPI);
sdSpi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI, SDCARD_CS);
pinMode(SDCARD_CS, OUTPUT);
digitalWrite(SDCARD_CS, HIGH);
pinMode(P_LORA_NSS, OUTPUT);
digitalWrite(P_LORA_NSS, HIGH);
delay(100);
bool mounted = false;
for (int attempt = 0; attempt < 3 && !mounted; attempt++) {
if (attempt > 0) {
digitalWrite(SDCARD_CS, HIGH);
delay(250);
Serial.printf("setup() - SD card retry %d/3\n", attempt + 1);
}
mounted = SD.begin(SDCARD_CS, sdSpi, 4000000);
}
if (mounted) {
sdCardReady = true;
Serial.println("setup() - SD card initialized");
} else {
Serial.println("setup() - SD card not available");
}
}
#endif
MESH_DEBUG_PRINTLN("setup() - about to call store.begin()");
@@ -759,6 +958,18 @@ void setup() {
}
#endif
// Initialize GT911 touch (T5S3 E-Paper Pro)
#if defined(LilyGo_T5S3_EPaper_Pro)
gt911Touch.setPins(GT911_PIN_RST, GT911_PIN_INT);
pinMode(GT911_PIN_INT, INPUT_PULLUP); // Ensure INT pin has pullup for clean transitions
if (gt911Touch.begin(Wire, GT911_SLAVE_ADDRESS_L, GT911_PIN_SDA, GT911_PIN_SCL)) {
gt911Ready = true;
Serial.println("setup() - GT911 touch initialized");
} else {
Serial.println("setup() - GT911 touch FAILED");
}
#endif
// ---------------------------------------------------------------------------
// SD card is already initialized (early init above).
// Now set up SD-dependent features: message history + text reader.
@@ -826,8 +1037,36 @@ void setup() {
}
#endif
// ---------------------------------------------------------------------------
// First-boot onboarding detection
// T5S3 SD-dependent features
#if defined(LilyGo_T5S3_EPaper_Pro) && defined(HAS_SDCARD)
if (sdCardReady) {
// Channel message history
ChannelScreen* chanScr = (ChannelScreen*)ui_task.getChannelScreen();
if (chanScr) {
chanScr->setSDReady(true);
if (chanScr->loadFromSD()) {
Serial.println("setup() - Message history loaded from SD");
}
}
// Text reader — set SD ready and pre-index books
TextReaderScreen* reader = (TextReaderScreen*)ui_task.getTextReaderScreen();
if (reader) {
reader->setSDReady(true);
if (disp) {
cpuPower.setBoost();
reader->bootIndex(*disp);
}
}
// Notes screen
NotesScreen* notesScr = (NotesScreen*)ui_task.getNotesScreen();
if (notesScr) {
notesScr->setSDReady(true);
}
Serial.println("setup() - SD features initialized");
}
#endif
// Check if node name is still the default hex prefix (first 4 bytes of pub key)
// If so, launch onboarding wizard to set name and radio preset
// ---------------------------------------------------------------------------
@@ -1120,6 +1359,102 @@ void loop() {
handleKeyboardInput();
#endif
// ---------------------------------------------------------------------------
// T5S3 GT911 Touch Input — tap/swipe/long-press state machine
// Gestures:
// Tap = finger down + up with minimal movement → select/open
// Swipe = finger drag > threshold → scroll/page turn
// Long press = finger held > 500ms without moving → edit/enter
// After processing an event, cooldown waits for finger lift before next event.
// ---------------------------------------------------------------------------
#if defined(LilyGo_T5S3_EPaper_Pro)
{
int16_t tx, ty;
bool gotPoint = readTouchLandscape(&tx, &ty);
unsigned long now = millis();
if (gotPoint) {
lastTouchSeenMs = now; // Track when we last saw a valid touch report
}
// Determine if finger is "present" — GT911 getPoint() only returns true
// once per report cycle (~10ms), then returns false until the next report.
// During a blocking e-ink refresh (~1s), many cycles are missed.
// So "finger lifted" = no valid report for TOUCH_LIFT_DEBOUNCE_MS.
bool fingerPresent = (now - lastTouchSeenMs) < TOUCH_LIFT_DEBOUNCE_MS;
// Rate limit — after processing an event, wait for finger lift + cooldown
if (touchCooldown) {
if (!fingerPresent && (now - lastTouchEventMs) >= TOUCH_MIN_INTERVAL_MS) {
touchCooldown = false;
touchDown = false;
}
}
else if (gotPoint && !touchDown) {
// Finger just touched down (first valid report)
touchDown = true;
touchDownTime = now;
touchDownX = tx;
touchDownY = ty;
touchLastX = tx;
touchLastY = ty;
longPressHandled = false;
swipeHandled = false;
}
else if (touchDown && fingerPresent) {
// Finger still down — update position if we got a new point
if (gotPoint) {
touchLastX = tx;
touchLastY = ty;
}
int16_t dx = touchLastX - touchDownX;
int16_t dy = touchLastY - touchDownY;
int16_t dist = abs(dx) > abs(dy) ? abs(dx) : abs(dy);
// Swipe detection — fire once when threshold exceeded
if (!swipeHandled && !longPressHandled && dist >= TOUCH_SWIPE_THRESHOLD) {
swipeHandled = true;
Serial.printf("[Touch] SWIPE dx=%d dy=%d\n", dx, dy);
char c = mapTouchSwipe(dx, dy);
if (c) {
ui_task.injectKey(c);
cpuPower.setBoost();
}
lastTouchEventMs = now;
touchCooldown = true;
}
// Long press — only if finger hasn't moved much
else if (!longPressHandled && !swipeHandled && dist < TOUCH_SWIPE_THRESHOLD &&
(now - touchDownTime) >= TOUCH_LONG_PRESS_MS) {
longPressHandled = true;
Serial.printf("[Touch] LONG PRESS at (%d,%d)\n", touchDownX, touchDownY);
char c = mapTouchLongPress(touchDownX, touchDownY);
if (c) {
ui_task.injectKey(c);
cpuPower.setBoost();
}
lastTouchEventMs = now;
touchCooldown = true;
}
}
else if (touchDown && !fingerPresent) {
// Finger lifted (no report for TOUCH_LIFT_DEBOUNCE_MS)
touchDown = false;
if (!longPressHandled && !swipeHandled) {
Serial.printf("[Touch] TAP at (%d,%d)\n", touchDownX, touchDownY);
char c = mapTouchTap(touchDownX, touchDownY);
if (c) {
ui_task.injectKey(c);
}
cpuPower.setBoost();
lastTouchEventMs = now;
touchCooldown = true;
}
}
}
#endif
// Poll touch input for phone dialer numpad
// Hybrid debounce: finger-up detection + 150ms minimum between accepted taps.
// The CST328 INT pin is pulse-based (not level), so getPoint() can return
@@ -651,7 +651,7 @@ public:
display.setCursor(display.width() - display.getTextWidth(copyHint) - 2, footerY);
display.print(copyHint);
#if AUTO_OFF_MILLIS == 0
#ifdef USE_EINK
return 5000;
#else
return 1000;
@@ -735,7 +735,11 @@ public:
int availH = maxY - y;
if (maxFillH > availH) maxFillH = availH;
display.setColor(DisplayDriver::LIGHT);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.fillRect(0, y, contentW, maxFillH);
#else
display.fillRect(0, y + 5, contentW, maxFillH);
#endif
}
// Time indicator with hop count - inline on same line as message start
@@ -888,7 +892,11 @@ public:
if (maxFillH > availH) maxFillH = availH;
if (usedH < maxFillH) {
display.setColor(DisplayDriver::DARK);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.fillRect(0, y, contentW, maxFillH - usedH);
#else
display.fillRect(0, y + 5, contentW, maxFillH - usedH);
#endif
}
}
@@ -943,6 +951,10 @@ public:
display.setCursor(0, footerY);
display.setColor(DisplayDriver::YELLOW);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.setTextSize(0);
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Select Boot: Home");
#else
// Left side: abbreviated controls
if (_replySelectMode) {
display.print("W/S:Sel V:Pth Q:X");
@@ -955,8 +967,9 @@ public:
display.setCursor(display.width() - display.getTextWidth(rightText) - 2, footerY);
display.print(rightText);
}
#endif
#if AUTO_OFF_MILLIS == 0 // e-ink
#ifdef USE_EINK
return 5000;
#else
return 1000;
@@ -237,7 +237,11 @@ public:
// Highlight: fill LIGHT rect first, then draw DARK text on top
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 {
display.setColor(DisplayDriver::LIGHT);
@@ -297,6 +301,10 @@ public:
display.drawRect(0, footerY - 2, display.width(), 1);
display.setColor(DisplayDriver::YELLOW);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.setTextSize(0);
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Select Boot: Home");
#else
// Left: Q:Bk
display.setCursor(0, footerY);
display.print("Q:Bk");
@@ -310,6 +318,7 @@ public:
const char* right = "F:Dscvr";
display.setCursor(display.width() - display.getTextWidth(right) - 2, footerY);
display.print(right);
#endif
return 5000; // e-ink: next render after 5s
}
@@ -586,7 +586,13 @@ public:
// Selection highlight
if (selected) {
display.setColor(DisplayDriver::LIGHT);
#if defined(LilyGo_T5S3_EPaper_Pro)
// FreeSans12pt: baseline at (y+5)*scale_y, ascent ~17px above.
// Highlight needs to start above the baseline to cover ascenders.
display.fillRect(0, y, display.width(), lineHeight);
#else
display.fillRect(0, y + 5, display.width(), lineHeight);
#endif
display.setColor(DisplayDriver::DARK);
} else {
display.setColor(DisplayDriver::LIGHT);
@@ -992,6 +998,14 @@ public:
display.setColor(DisplayDriver::YELLOW);
display.setCursor(0, footerY);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.setTextSize(0);
if (_editMode == EDIT_NONE) {
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Select Hold: Edit Boot: Home");
} else {
display.print("Editing...");
}
#else
if (_editMode == EDIT_TEXT) {
display.print("Type, Enter:Ok Q:Cancel");
#ifdef MECK_WIFI_COMPANION
@@ -1020,6 +1034,7 @@ public:
display.setCursor(display.width() - display.getTextWidth(r) - 2, footerY);
display.print(r);
}
#endif
return _editMode != EDIT_NONE ? 700 : 1000;
}
@@ -863,9 +863,13 @@ private:
if (selected) {
display.setColor(DisplayDriver::LIGHT);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.fillRect(0, y, display.width(), listLineH);
#else
// setCursor adds +5 to y internally, but fillRect does not.
// Offset fillRect by +5 to align highlight bar with text.
display.fillRect(0, y + 5, display.width(), listLineH);
#endif
display.setColor(DisplayDriver::DARK);
} else {
display.setColor(DisplayDriver::LIGHT);
@@ -918,13 +922,19 @@ private:
display.setTextSize(1);
int footerY = display.height() - 12;
display.drawRect(0, footerY - 2, display.width(), 1);
display.setCursor(0, footerY);
display.setColor(DisplayDriver::YELLOW);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.setTextSize(0);
display.drawTextCentered(display.width() / 2, footerY, "Swipe: Scroll Tap: Open Boot: Home");
#else
display.setCursor(0, footerY);
display.print("Q:Back W/S:Nav");
const char* right = "Ent:Open";
display.setCursor(display.width() - display.getTextWidth(right) - 2, footerY);
display.print(right);
#endif
}
void renderPage(DisplayDriver& display) {
@@ -1002,12 +1012,22 @@ private:
char status[30];
int pct = _totalPages > 1 ? (_currentPage * 100) / (_totalPages - 1) : 100;
sprintf(status, "%d/%d %d%%", _currentPage + 1, _totalPages, pct);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.setTextSize(0);
display.setCursor(0, footerY);
display.print(status);
const char* right = "Swipe: Page Tap: Next Hold: Close";
display.setCursor(display.width() - display.getTextWidth(right) - 2, footerY);
display.print(right);
#else
display.setCursor(0, footerY);
display.print(status);
const char* right = "W/S:Nav Q:Back";
display.setCursor(display.width() - display.getTextWidth(right) - 2, footerY);
display.print(right);
#endif
}
public:
@@ -1036,8 +1056,22 @@ public:
if (tenCharsW > 0) {
_charsPerLine = (display.width() * 10) / tenCharsW;
}
#if defined(LilyGo_T5S3_EPaper_Pro)
// FreeSans12pt is proportional — "M" is the widest character.
// Using M-width gives ~56 chars/line but actual text only fills 60% of screen.
// Re-measure with representative lowercase text for realistic average width.
{
uint16_t sampleW = display.getTextWidth("abcdefghijklmno"); // 15 chars
if (sampleW > 0) {
_charsPerLine = (display.width() * 15) / sampleW;
}
}
if (_charsPerLine < 15) _charsPerLine = 15;
if (_charsPerLine > 120) _charsPerLine = 120; // Proportional fonts can fit many chars
#else
if (_charsPerLine < 15) _charsPerLine = 15;
if (_charsPerLine > 60) _charsPerLine = 60;
#endif
// Line height for built-in 6x8 font:
// setCursor adds +5 to y, so effective text top = (y+5)*scale_y
@@ -1052,6 +1086,13 @@ public:
_lineHeight = 5; // Safe fallback
}
#if defined(LilyGo_T5S3_EPaper_Pro)
// T5S3 uses FreeSans12pt/FreeSerif12pt for size 0 (yAdvance=29px).
// Line height in virtual coords: 29px / scale_y(4.22) ≈ 7 units.
// Add 1 unit for comfortable spacing.
_lineHeight = 8;
#endif
_headerHeight = 0; // No header in reading mode (maximize text area)
_footerHeight = 14;
int textAreaHeight = display.height() - _headerHeight - _footerHeight;
+77 -5
View File
@@ -245,6 +245,9 @@ public:
int render(DisplayDriver& display) override {
char tmp[80];
#if defined(LilyGo_T5S3_EPaper_Pro)
_task->setHomeShowingTiles(false); // Reset — only set true on FIRST page
#endif
// node name (tinyfont to avoid overlapping clock)
display.setTextSize(0);
display.setColor(DisplayDriver::GREEN);
@@ -299,6 +302,9 @@ public:
}
if (_page == HomePage::FIRST) {
#if defined(LilyGo_T5S3_EPaper_Pro)
_task->setHomeShowingTiles(true);
#endif
int y = 20;
display.setColor(DisplayDriver::YELLOW);
display.setTextSize(2);
@@ -332,6 +338,54 @@ public:
}
#endif
// ----- T5S3: Tappable tile grid (touch-friendly home screen) -----
#if defined(LilyGo_T5S3_EPaper_Pro)
// 3×2 grid of tiles below MSG count
// Virtual coords (128×128), scaled by DisplayDriver
{
struct Tile { const char* letter; const char* label; };
const Tile tiles[2][3] = {
{ {"M", "Messages"}, {"C", "Contacts"}, {"S", "Settings"} },
{ {"E", "Reader"}, {"N", "Notes"}, {"D", "Discover"} }
};
const int tileW = 40;
const int tileH = 32;
const int gapX = 1;
const int gapY = 2;
const int gridW = tileW * 3 + gapX * 2;
const int gridX = (display.width() - gridW) / 2;
const int gridY = y + 2;
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 3; col++) {
int tx = gridX + col * (tileW + gapX);
int ty = gridY + row * (tileH + gapY);
// Tile border
display.setColor(DisplayDriver::LIGHT);
display.drawRect(tx, ty, tileW, tileH);
// Letter centered in tile (pushed down for vertical centering)
display.setTextSize(2);
display.drawTextCentered(tx + tileW / 2, ty + 10, tiles[row][col].letter);
// Label centered below letter
display.setTextSize(0);
display.drawTextCentered(tx + tileW / 2, ty + 20, tiles[row][col].label);
}
}
// Nav hint below grid
y = gridY + 2 * tileH + gapY + 2;
display.setColor(DisplayDriver::GREEN);
display.setTextSize(0);
display.drawTextCentered(display.width() / 2, y, "Tap tile to open");
}
display.setTextSize(1);
#else
// ----- T-Deck Pro: Keyboard shortcut text menu -----
// Menu shortcuts - tinyfont monospaced grid
y += 6;
display.setColor(DisplayDriver::LIGHT);
@@ -365,12 +419,9 @@ public:
// Nav hint
display.setColor(DisplayDriver::GREEN);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.drawTextCentered(display.width() / 2, y, "Tap screen to cycle home views");
#else
display.drawTextCentered(display.width() / 2, y, "Press A/D to cycle home views");
#endif
display.setTextSize(1); // restore
#endif
} else if (_page == HomePage::RECENT) {
the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE);
display.setColor(DisplayDriver::GREEN);
@@ -1188,7 +1239,28 @@ void UITask::loop() {
#elif defined(PIN_USER_BTN)
int ev = user_btn.check();
if (ev == BUTTON_EVENT_CLICK) {
#if defined(LilyGo_T5S3_EPaper_Pro)
// T5S3: single click = cycle pages on home, go back to home from elsewhere
if (curr == home) {
c = checkDisplayOn(KEY_NEXT);
} else {
// Navigate back: reader reading→file list, file list→home, others→home
if (isOnTextReader()) {
TextReaderScreen* reader = (TextReaderScreen*)text_reader;
if (reader && reader->isReading()) {
c = checkDisplayOn('q'); // reading mode: close book → file list
} else {
gotoHomeScreen(); // file list: go home
c = 0;
}
} else {
gotoHomeScreen();
c = 0; // consumed
}
}
#else
c = checkDisplayOn(KEY_NEXT);
#endif
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
c = handleLongPress(KEY_ENTER);
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
@@ -1398,7 +1470,7 @@ char UITask::handleTripleClick(char c) {
if (board.isBacklightOn()) {
board.setBacklight(false); // If already on, turn off
} else {
board.setBacklightBrightness(80);
board.setBacklightBrightness(4);
board.setBacklight(true);
}
#else
+4
View File
@@ -85,6 +85,7 @@ class UITask : public AbstractUITask {
#endif
UIScreen* map_screen; // Map tile screen (GPS + SD card tiles)
UIScreen* curr;
bool _homeShowingTiles = false; // Set by HomeScreen render when tile grid is visible
void userLedHandler();
@@ -147,6 +148,9 @@ public:
bool isOnChannelScreen() const { return curr == channel_screen; }
bool isOnContactsScreen() const { return curr == contacts_screen; }
bool isOnTextReader() const { return curr == text_reader; } // *** NEW ***
bool isOnHomeScreen() const { return curr == home; }
bool isHomeShowingTiles() const { return _homeShowingTiles; }
void setHomeShowingTiles(bool v) { _homeShowingTiles = v; }
bool isOnNotesScreen() const { return curr == notes_screen; }
bool isOnSettingsScreen() const { return curr == settings_screen; }
bool isOnAudiobookPlayer() const { return curr == audiobook_screen; }
+26 -17
View File
@@ -78,7 +78,11 @@ bool FastEPDDisplay::begin() {
// Set canvas defaults
_canvas->fillScreen(1); // White background (bit=1 → white in FastEPD)
_canvas->setTextColor(0); // Black text (bit=0 → black in FastEPD)
_canvas->setFont(&FreeSans24pt7b);
#ifdef MECK_SERIF_FONT
_canvas->setFont(&FreeSerif12pt7b);
#else
_canvas->setFont(&FreeSans12pt7b);
#endif
_canvas->setTextWrap(false);
_curr_color = GxEPD_BLACK;
@@ -118,28 +122,34 @@ void FastEPDDisplay::setTextSize(int sz) {
_frameCRC.update<int>(sz);
// Font mapping for 960×540 display at ~234 DPI
// The T-Deck Pro at 240×320 (~140 DPI) uses FreeSans9pt for body text.
// At 234 DPI we need roughly 2.5× larger fonts for equivalent physical size.
// Built-in 5×7 font scaled 4× = 20×28px — readable for status bar items.
// Toggle between font families via -D MECK_SERIF_FONT build flag
switch(sz) {
case 0: // Tiny — node name, clock, battery %, menu shortcuts
_canvas->setFont(NULL);
_canvas->setTextSize(4); // 5×7 × 4 = 20×28 physical pixels
break;
case 1: // Small/normal — body text, contact list items
_canvas->setFont(&FreeSans24pt7b);
case 0: // Body text — reader content, settings rows, messages, footers
#ifdef MECK_SERIF_FONT
_canvas->setFont(&FreeSerif12pt7b);
#else
_canvas->setFont(&FreeSans12pt7b);
#endif
_canvas->setTextSize(1);
break;
case 2: // Medium bold — MSG count, headings, labels
_canvas->setFont(&FreeSansBold24pt7b);
case 1: // Headings — screen titles, channel names (bold, same height as body)
_canvas->setFont(&FreeSansBold12pt7b);
_canvas->setTextSize(1);
break;
case 3: // Large — splash screen title, onboarding
case 2: // Large bold — MSG count, tile letters
_canvas->setFont(&FreeSansBold18pt7b);
_canvas->setTextSize(1);
break;
case 3: // Extra large — splash screen title
_canvas->setFont(&FreeSansBold24pt7b);
_canvas->setTextSize(1);
break;
default:
_canvas->setFont(&FreeSans24pt7b);
#ifdef MECK_SERIF_FONT
_canvas->setFont(&FreeSerif12pt7b);
#else
_canvas->setFont(&FreeSans12pt7b);
#endif
_canvas->setTextSize(1);
break;
}
@@ -166,9 +176,8 @@ void FastEPDDisplay::setCursor(int x, int y) {
_frameCRC.update<int>(x);
_frameCRC.update<int>(y);
// Scale virtual coordinates to physical, with baseline offset
// The +5 pushes text baseline down so ascenders don't overlap elements above
// (Same convention as GxEPDDisplay for T-Deck Pro)
// Scale virtual coordinates to physical, with baseline offset.
// The +5 pushes text baseline down so ascenders at y=0 are visible.
_canvas->setCursor(
(int)((x + offset_x) * scale_x),
(int)((y + offset_y + 5) * scale_y)
+3 -1
View File
@@ -21,6 +21,8 @@
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold24pt7b.h>
#include <Fonts/FreeSerif12pt7b.h>
#include <Fonts/FreeSerif18pt7b.h>
#include "DisplayDriver.h"
@@ -110,7 +112,7 @@ public:
void drawTextRaw(int16_t x, int16_t y, const char* text, uint16_t color) {
if (!_canvas) return;
_canvas->setFont(NULL);
_canvas->setTextSize(4); // 4× built-in 5×7 = 20×28, readable on 960×540
_canvas->setTextSize(3); // 3× built-in 5×7 = 15×21, readable on 960×540
_canvas->setTextColor(color ? 1 : 0);
_canvas->setCursor(x, y);
_canvas->print(text);
@@ -77,6 +77,9 @@ build_flags =
-D OFFLINE_QUEUE_SIZE=256
-D DISPLAY_CLASS=FastEPDDisplay
-D USE_EINK
; Font family: comment/uncomment to toggle (delete .indexes on SD after switching)
; -D MECK_SERIF_FONT ; FreeSerif (Times New Roman-like)
; ; Default (no flag): FreeSans (Arial-like)
build_src_filter = ${LilyGo_T5S3_EPaper_Pro.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
@@ -88,6 +91,7 @@ lib_deps =
densaugeo/base64 @ ~1.4.0
adafruit/Adafruit GFX Library@^1.11.0
https://github.com/mverch67/FastEPD/archive/0df1bff329b6fc782e062f611758880762340647.zip
https://github.com/lewisxhe/SensorLib/archive/refs/tags/v0.3.4.zip
; ---------------------------------------------------------------------------
; Phase 3+ variants (uncomment when touch input is implemented)