mirror of
https://github.com/pelgraine/Meck.git
synced 2026-08-10 18:52:44 +02:00
New Max Home UI design
This commit is contained in:
@@ -1312,6 +1312,78 @@ static void lastHeardToggleContact() {
|
||||
|
||||
// Home screen FIRST page: tile taps (virtual coordinate hit test)
|
||||
if (ui_task.isOnHomeScreen() && ui_task.isHomeShowingTiles()) {
|
||||
#if defined(LilyGo_TDeck_Pro_Max)
|
||||
// MAX: 2-column tile grid, 6 paired rows + full-width Phone row.
|
||||
// Hit-tested in RAW PHYSICAL pixels (240x320) using the untranslated
|
||||
// touch coords -- geometry must stay in sync with the render block in
|
||||
// UITask.cpp.
|
||||
const int tileW = 111, tileH = 32, gapX = 5, gapY = 3;
|
||||
const int gridX = 6;
|
||||
const int gridY = 68;
|
||||
const int pitch = tileH + gapY;
|
||||
|
||||
if (y >= gridY && y < gridY + 6 * pitch + tileH) {
|
||||
int row = (y - gridY) / pitch;
|
||||
if (row > 6) row = 6;
|
||||
if (row == 6) {
|
||||
#ifdef HAS_4G_MODEM
|
||||
ui_task.gotoSMSScreen();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
int col = (x - gridX) / (tileW + gapX);
|
||||
if (col < 0) col = 0;
|
||||
if (col > 1) col = 1;
|
||||
switch (row * 2 + col) {
|
||||
case 0: ui_task.gotoChannelPickerScreen(); return 0;
|
||||
case 1: ui_task.gotoContactsScreen(); return 0;
|
||||
case 2: ui_task.gotoSettingsScreen(); return 0;
|
||||
case 3: ui_task.gotoDiscoveryScreen(); return 0;
|
||||
case 4: ui_task.gotoTraceScreen(); return 0;
|
||||
case 5:
|
||||
#if HAS_GPS
|
||||
ui_task.gotoMapScreen();
|
||||
#endif
|
||||
return 0;
|
||||
case 6: ui_task.gotoNotesScreen(); return 0;
|
||||
case 7: ui_task.gotoTextReader(); return 0;
|
||||
case 8:
|
||||
#if !defined(HAS_4G_MODEM) || defined(MECK_AUDIO_VARIANT)
|
||||
// Audiobooks: lazy-init Audio + screen on first use (mirrors 'p' key handler)
|
||||
if (!ui_task.getAudiobookScreen()) {
|
||||
audio = new Audio();
|
||||
AudiobookPlayerScreen* abScreen = new AudiobookPlayerScreen(&ui_task, audio, the_mesh.getNodePrefs());
|
||||
abScreen->setSDReady(sdCardReady);
|
||||
ui_task.setAudiobookScreen(abScreen);
|
||||
}
|
||||
ui_task.gotoAudiobookPlayer();
|
||||
#endif
|
||||
return 0;
|
||||
case 9:
|
||||
#ifdef MECK_AUDIO_VARIANT
|
||||
// Alarm: ensure Audio* exists (mirrors 'k' key handler)
|
||||
if (!audio) {
|
||||
audio = new Audio();
|
||||
}
|
||||
{
|
||||
AlarmScreen* alarmScr = (AlarmScreen*)ui_task.getAlarmScreen();
|
||||
if (alarmScr) alarmScr->setAudio(audio);
|
||||
}
|
||||
ui_task.gotoAlarmScreen();
|
||||
#endif
|
||||
return 0;
|
||||
case 10:
|
||||
#ifdef MECK_WEB_READER
|
||||
ui_task.gotoWebReader();
|
||||
#endif
|
||||
return 0;
|
||||
case 11: ui_task.gotoGamesMenu(); return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// Tap outside tiles -- left half backward, right half forward (physical)
|
||||
return (x < 120) ? (char)KEY_PREV : (char)KEY_NEXT;
|
||||
#else
|
||||
const int tileW = 40, tileH = 22, gapX = 1, gapY = 1;
|
||||
const int gridW = tileW * 3 + gapX * 2;
|
||||
const int gridX = (128 - gridW) / 2; // =3
|
||||
@@ -1341,6 +1413,7 @@ static void lastHeardToggleContact() {
|
||||
}
|
||||
// Tap outside tiles — left half backward, right half forward
|
||||
return (vx < 64) ? (char)KEY_PREV : (char)KEY_NEXT;
|
||||
#endif // LilyGo_TDeck_Pro_Max
|
||||
}
|
||||
|
||||
// Home screen (non-tile pages): left half taps backward, right half forward
|
||||
|
||||
@@ -81,6 +81,30 @@ static const uint8_t icon_star[] PROGMEM = {
|
||||
0x1F,0x80, 0x3F,0xC0, 0x39,0xC0, 0x70,0xE0, 0x60,0x60, 0x00,0x00,
|
||||
};
|
||||
|
||||
// Headphones (Audiobooks) -- 12x12, MSB-first, 2 bytes per row
|
||||
static const uint8_t icon_headphones[] PROGMEM = {
|
||||
0x00,0x00, 0x1F,0x80, 0x20,0x40, 0x40,0x20, 0x40,0x20, 0x40,0x20,
|
||||
0xE0,0x70, 0xE0,0x70, 0xE0,0x70, 0x60,0x60, 0x00,0x00, 0x00,0x00,
|
||||
};
|
||||
|
||||
// Globe (Browser) -- 12x12, MSB-first, 2 bytes per row
|
||||
static const uint8_t icon_globe[] PROGMEM = {
|
||||
0x1F,0x80, 0x26,0x40, 0x46,0x20, 0x86,0x10, 0x86,0x10, 0xFF,0xF0,
|
||||
0xFF,0xF0, 0x86,0x10, 0x86,0x10, 0x46,0x20, 0x26,0x40, 0x1F,0x80,
|
||||
};
|
||||
|
||||
// Bell (Alarm) -- 12x12 home tile icon, MSB-first, 2 bytes per row
|
||||
static const uint8_t icon_bell[] PROGMEM = {
|
||||
0x06,0x00, 0x1F,0x80, 0x3F,0xC0, 0x3F,0xC0, 0x3F,0xC0, 0x3F,0xC0,
|
||||
0x3F,0xC0, 0x7F,0xE0, 0xFF,0xF0, 0x00,0x00, 0x06,0x00, 0x00,0x00,
|
||||
};
|
||||
|
||||
// Phone (SMS / dialler) -- 12x12, MSB-first, 2 bytes per row
|
||||
static const uint8_t icon_phone[] PROGMEM = {
|
||||
0x1F,0x80, 0x20,0x40, 0x20,0x40, 0x20,0x40, 0x20,0x40, 0x20,0x40,
|
||||
0x20,0x40, 0x20,0x40, 0x20,0x40, 0x26,0x40, 0x20,0x40, 0x1F,0x80,
|
||||
};
|
||||
|
||||
// 🔔 Bell -- 7x8 status bar indicator (alarm enabled)
|
||||
// MSB-first, 1 byte per row
|
||||
#define BELL_ICON_W 7
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#if defined(LilyGo_TDeck_Pro_Max)
|
||||
#include "DRV2605Haptic.h" // haptic motor for "Buzzer (vibrate)" channels
|
||||
#endif
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(MECK_AUDIO_VARIANT)
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(MECK_AUDIO_VARIANT) || defined(LilyGo_TDeck_Pro_Max)
|
||||
#include "HomeIcons.h"
|
||||
#endif
|
||||
#if defined(WIFI_SSID) || defined(MECK_WIFI_COMPANION)
|
||||
@@ -368,7 +368,7 @@ public:
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
char tmp[80];
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro_Max)
|
||||
_task->setHomeShowingTiles(false); // Reset — only set true on FIRST page
|
||||
#endif
|
||||
|
||||
@@ -453,6 +453,8 @@ public:
|
||||
int y = 13; // Below header
|
||||
#elif defined(LilyGo_T5S3_EPaper_Pro)
|
||||
int y = 14; // Closer to header
|
||||
#elif defined(LilyGo_TDeck_Pro_Max)
|
||||
int y = 8; // Tighter under header; frees room for the MSG strip above the tile grid
|
||||
#else
|
||||
int y = 14;
|
||||
#endif
|
||||
@@ -466,9 +468,100 @@ public:
|
||||
}
|
||||
|
||||
if (_page == HomePage::FIRST) {
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro_Max)
|
||||
_task->setHomeShowingTiles(true);
|
||||
#endif
|
||||
#if defined(LilyGo_TDeck_Pro_Max)
|
||||
// ----- MAX: Touch tile grid home screen (T-Watch / P4 style) -----
|
||||
// Rendered in RAW PHYSICAL pixels (240x320) via GxEPDDisplay raw
|
||||
// helpers, bypassing the 128x128 virtual scaling so icons stay 1:1
|
||||
// and the dithered grey borders read as continuous bands.
|
||||
// Border shades approximate greys with 1-bit dither (0 = solid,
|
||||
// 1 = dark grey, 2 = light grey), cycling so no row pairs two
|
||||
// identical shades and no column stacks the same shade.
|
||||
// Layout must stay in sync with the touch hit-test in main.cpp
|
||||
// mapTouchTap().
|
||||
{
|
||||
GxEPDDisplay* eink = static_cast<GxEPDDisplay*>(&display);
|
||||
const uint16_t fg = eink->rawFgColor();
|
||||
struct MaxTile { const uint8_t* icon; const char* label; uint8_t shade; };
|
||||
static const MaxTile tiles[12] = {
|
||||
{ icon_envelope, "Messages", 0 }, { icon_people, "Contacts", 1 },
|
||||
{ icon_gear, "Settings", 2 }, { icon_search, "Discover", 0 },
|
||||
{ icon_trace, "Trace", 1 }, { icon_map, "Maps", 2 },
|
||||
{ icon_notepad, "Notes", 0 }, { icon_book, "Reader", 1 },
|
||||
{ icon_headphones, "Audiobooks", 2 }, { icon_bell, "Alarm", 0 },
|
||||
{ icon_globe, "Browser", 1 }, { icon_gamepad, "Games", 2 },
|
||||
};
|
||||
// Physical layout matching the approved mockup: 2 cols x 6 rows
|
||||
// plus a full-width Phone tile, icon stacked above the label.
|
||||
// Labels honour the user font style (Classic 5x7 / Noto 7pt /
|
||||
// Montserrat 7pt) via drawTextRawStyled.
|
||||
const int tileW = 111, tileH = 32, gapX = 5, gapY = 3;
|
||||
const int gridX = 6;
|
||||
const int gridY = 68; // below the status strip at y=46 (page dots render at physical ~30-38 with the Max dot row at virtual y=8)
|
||||
const int radius = 6;
|
||||
const int borderT = 3; // 3px band so the dither reads as grey
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
int row = i / 2, col = i % 2;
|
||||
int tx = gridX + col * (tileW + gapX);
|
||||
int ty = gridY + row * (tileH + gapY);
|
||||
eink->drawRoundRectShadedRaw(tx, ty, tileW, tileH, radius, borderT, tiles[i].shade, fg);
|
||||
eink->drawXbmRaw(tx + (tileW - HOME_ICON_W) / 2, ty + 5, tiles[i].icon, HOME_ICON_W, HOME_ICON_H, fg);
|
||||
int lw = eink->measureTextRawStyled(tiles[i].label);
|
||||
eink->drawTextRawStyled(tx + (tileW - lw) / 2, ty + 17, tiles[i].label, fg);
|
||||
}
|
||||
|
||||
// Full-width Phone tile (row 6)
|
||||
{
|
||||
int ty = gridY + 6 * (tileH + gapY);
|
||||
int tw = tileW * 2 + gapX;
|
||||
eink->drawRoundRectShadedRaw(gridX, ty, tw, tileH, radius, borderT, 0, fg);
|
||||
eink->drawXbmRaw(gridX + (tw - HOME_ICON_W) / 2, ty + 5, icon_phone, HOME_ICON_W, HOME_ICON_H, fg);
|
||||
int lw = eink->measureTextRawStyled("Phone");
|
||||
eink->drawTextRawStyled(gridX + (tw - lw) / 2, ty + 17, "Phone", fg);
|
||||
}
|
||||
|
||||
// Top status strip (physical, between page dots and grid): unread
|
||||
// count plus connection state / WiFi IP / BLE pin
|
||||
{
|
||||
sprintf(tmp, "MSG: %d", _task->getUnreadMsgCount());
|
||||
char rightBuf[40];
|
||||
rightBuf[0] = 0;
|
||||
#if defined(BLE_PIN_CODE) || defined(WIFI_SSID) || defined(MECK_WIFI_COMPANION)
|
||||
if (_task->hasConnection()) {
|
||||
strcpy(rightBuf, "< Connected >");
|
||||
}
|
||||
#endif
|
||||
#if defined(WIFI_SSID) || defined(MECK_WIFI_COMPANION)
|
||||
if (rightBuf[0] == 0) {
|
||||
IPAddress ip = WiFi.localIP();
|
||||
if (ip != IPAddress(0,0,0,0)) {
|
||||
snprintf(rightBuf, sizeof(rightBuf), "IP: %d.%d.%d.%d:%d", ip[0], ip[1], ip[2], ip[3], TCP_PORT);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef BLE_PIN_CODE
|
||||
if (rightBuf[0] == 0 && _task->isSerialEnabled() && the_mesh.getBLEPin() != 0) {
|
||||
sprintf(rightBuf, "Pin:%d", the_mesh.getBLEPin());
|
||||
}
|
||||
#endif
|
||||
int stripY = 46;
|
||||
int mw = eink->measureTextRawStyled(tmp);
|
||||
if (rightBuf[0]) {
|
||||
int rw = eink->measureTextRawStyled(rightBuf);
|
||||
int startX = (eink->rawWidth() - (mw + 12 + rw)) / 2;
|
||||
if (startX < 0) startX = 0;
|
||||
eink->drawTextRawStyled(startX, stripY, tmp, fg);
|
||||
eink->drawTextRawStyled(startX + mw + 12, stripY, rightBuf, fg);
|
||||
} else {
|
||||
eink->drawTextRawStyled((eink->rawWidth() - mw) / 2, stripY, tmp, fg);
|
||||
}
|
||||
}
|
||||
display.setTextSize(1); // restore driver font state after raw text
|
||||
}
|
||||
#else // not LilyGo_TDeck_Pro_Max
|
||||
#if defined(LilyGo_T5S3_EPaper_Pro)
|
||||
#if defined(BLE_PIN_CODE) || defined(WIFI_SSID) || defined(MECK_WIFI_COMPANION)
|
||||
int y = 18; // Tighter spacing — connectivity info fills gap below dots
|
||||
@@ -755,6 +848,7 @@ public:
|
||||
display.setTextSize(1); // restore
|
||||
#endif // LILYGO_TECHO_LITE
|
||||
#endif
|
||||
#endif // not LilyGo_TDeck_Pro_Max
|
||||
} else if (_page == HomePage::RECENT) {
|
||||
the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
|
||||
@@ -383,6 +383,138 @@ void GxEPDDisplay::drawRect(int x, int y, int w, int h) {
|
||||
display.drawRect((x+offset_x)*scale_x, (y+offset_y)*scale_y, w*scale_x, h*scale_y, _curr_color);
|
||||
}
|
||||
|
||||
// --- Rounded tile borders with 1-bit dither shading (greyscale approximation) ---
|
||||
// shade: 0 = solid, 1 = dark grey (~75% dither), 2 = light grey (50% checker)
|
||||
static inline bool meckDitherKeep(int px, int py, uint8_t shade) {
|
||||
if (shade == 0) return true;
|
||||
if (shade == 1) return ((px + py) & 3) != 3;
|
||||
return ((px + py) & 1) == 0;
|
||||
}
|
||||
|
||||
void GxEPDDisplay::plotShadedPixel(int px, int py, uint8_t shade, uint16_t color) {
|
||||
if (meckDitherKeep(px, py, shade)) display.drawPixel(px, py, color);
|
||||
}
|
||||
|
||||
// Rasterise a 1-physical-pixel rounded-rect outline: straight edges plus
|
||||
// midpoint-circle quarter arcs, every pixel passed through the dither mask
|
||||
void GxEPDDisplay::plotRoundOutline(int px, int py, int pw, int ph, int pr, uint8_t shade, uint16_t color) {
|
||||
if (pw <= 0 || ph <= 0) return;
|
||||
int maxr = ((pw < ph ? pw : ph) / 2) - 1;
|
||||
if (pr > maxr) pr = maxr;
|
||||
if (pr < 0) pr = 0;
|
||||
int x0 = px, y0 = py, x1 = px + pw - 1, y1 = py + ph - 1;
|
||||
|
||||
// Straight edges
|
||||
for (int i = x0 + pr; i <= x1 - pr; i++) {
|
||||
plotShadedPixel(i, y0, shade, color);
|
||||
plotShadedPixel(i, y1, shade, color);
|
||||
}
|
||||
for (int j = y0 + pr; j <= y1 - pr; j++) {
|
||||
plotShadedPixel(x0, j, shade, color);
|
||||
plotShadedPixel(x1, j, shade, color);
|
||||
}
|
||||
if (pr <= 0) return;
|
||||
|
||||
// Corner arcs: midpoint circle, one octant mirrored into four corners
|
||||
int cx0 = x0 + pr, cy0 = y0 + pr; // top-left corner centre
|
||||
int cx1 = x1 - pr, cy1 = y1 - pr; // bottom-right corner centre
|
||||
int f = 1 - pr;
|
||||
int ddF_x = 1;
|
||||
int ddF_y = -2 * pr;
|
||||
int xx = 0;
|
||||
int yy = pr;
|
||||
while (xx < yy) {
|
||||
if (f >= 0) { yy--; ddF_y += 2; f += ddF_y; }
|
||||
xx++; ddF_x += 2; f += ddF_x;
|
||||
plotShadedPixel(cx0 - xx, cy0 - yy, shade, color); // top-left
|
||||
plotShadedPixel(cx0 - yy, cy0 - xx, shade, color);
|
||||
plotShadedPixel(cx1 + xx, cy0 - yy, shade, color); // top-right
|
||||
plotShadedPixel(cx1 + yy, cy0 - xx, shade, color);
|
||||
plotShadedPixel(cx0 - xx, cy1 + yy, shade, color); // bottom-left
|
||||
plotShadedPixel(cx0 - yy, cy1 + xx, shade, color);
|
||||
plotShadedPixel(cx1 + xx, cy1 + yy, shade, color); // bottom-right
|
||||
plotShadedPixel(cx1 + yy, cy1 + xx, shade, color);
|
||||
}
|
||||
}
|
||||
|
||||
void GxEPDDisplay::drawRoundRectShadedRaw(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint8_t thickness, uint8_t shade, uint16_t color) {
|
||||
display_crc.update<int16_t>(x);
|
||||
display_crc.update<int16_t>(y);
|
||||
display_crc.update<int16_t>(w);
|
||||
display_crc.update<int16_t>(h);
|
||||
display_crc.update<int16_t>(r);
|
||||
display_crc.update<uint8_t>(thickness);
|
||||
display_crc.update<uint8_t>(shade);
|
||||
|
||||
// Border band: concentric 1px outlines, `thickness` px deep
|
||||
for (int t = 0; t < (int)thickness; t++) {
|
||||
plotRoundOutline(x + t, y + t, w - 2 * t, h - 2 * t, r - t, shade, color);
|
||||
}
|
||||
}
|
||||
|
||||
void GxEPDDisplay::drawXbmRaw(int16_t x, int16_t y, const uint8_t* bits, int w, int h, uint16_t color) {
|
||||
display_crc.update<int16_t>(x);
|
||||
display_crc.update<int16_t>(y);
|
||||
display_crc.update<uint8_t>(bits, ((w + 7) / 8) * h);
|
||||
|
||||
uint16_t widthInBytes = (w + 7) / 8;
|
||||
for (int by = 0; by < h; by++) {
|
||||
for (int bx = 0; bx < w; bx++) {
|
||||
uint16_t byteOffset = (by * widthInBytes) + (bx / 8);
|
||||
uint8_t bitMask = 0x80 >> (bx & 7);
|
||||
if (pgm_read_byte(bits + byteOffset) & bitMask) {
|
||||
display.drawPixel(x + bx, y + by, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GxEPDDisplay::drawTextRawTracked(int16_t x, int16_t y, const char* text, uint16_t color) {
|
||||
display_crc.update<int16_t>(x);
|
||||
display_crc.update<int16_t>(y);
|
||||
display_crc.update<uint8_t>((const uint8_t*)text, strlen(text));
|
||||
drawTextRaw(x, y, text, color);
|
||||
}
|
||||
|
||||
int16_t GxEPDDisplay::measureTextRawStyled(const char* text) {
|
||||
#ifdef HAS_MECK_FONTS
|
||||
const GFXfont* f = meckGetFont(_fontStyle, 0);
|
||||
if (f) {
|
||||
display.setFont(f);
|
||||
display.setTextSize(1);
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
|
||||
return (int16_t)(w + 1);
|
||||
}
|
||||
#endif
|
||||
return (int16_t)(6 * strlen(text)); // built-in 5x7, 6px advance
|
||||
}
|
||||
|
||||
void GxEPDDisplay::drawTextRawStyled(int16_t x, int16_t yTop, const char* text, uint16_t color) {
|
||||
#ifdef HAS_MECK_FONTS
|
||||
const GFXfont* f = meckGetFont(_fontStyle, 0);
|
||||
if (f) {
|
||||
display_crc.update<int16_t>(x);
|
||||
display_crc.update<int16_t>(yTop);
|
||||
display_crc.update<uint8_t>((const uint8_t*)text, strlen(text));
|
||||
display_crc.update<uint8_t>(_fontStyle);
|
||||
display.setFont(f);
|
||||
display.setTextSize(1);
|
||||
display.setTextColor(color);
|
||||
// Custom GFX fonts position by baseline: getTextBounds at (0,0) returns
|
||||
// a negative y1 (box top relative to baseline), so baseline = yTop - y1
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
|
||||
display.setCursor(x, yTop - y1);
|
||||
display.print(text);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
drawTextRawTracked(x, yTop, text, color);
|
||||
}
|
||||
|
||||
void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
display_crc.update<int>(x);
|
||||
display_crc.update<int>(y);
|
||||
|
||||
@@ -80,6 +80,11 @@ class GxEPDDisplay : public DisplayDriver {
|
||||
// Render one glyph from the current 8b font at the display's cursor position
|
||||
void drawGlyphAtCursor(uint16_t cp);
|
||||
|
||||
// Helpers for drawRoundRectShadedRaw: 1px rounded outline rasteriser
|
||||
// (physical coords) and dither-masked pixel plot
|
||||
void plotRoundOutline(int px, int py, int pw, int ph, int pr, uint8_t shade, uint16_t color);
|
||||
void plotShadedPixel(int px, int py, uint8_t shade, uint16_t color);
|
||||
|
||||
public:
|
||||
// Virtual canvas dimensions — default 128×128 (MeshCore standard).
|
||||
// Override for displays where physical resolution / scale < 128.
|
||||
@@ -134,6 +139,22 @@ public:
|
||||
display.print(text);
|
||||
}
|
||||
|
||||
// --- Raw (physical-pixel) tile-grid helpers for the MAX home screen ---
|
||||
// Foreground colour respecting dark mode (raw draws bypass setColor mapping)
|
||||
uint16_t rawFgColor() const { return _darkMode ? GxEPD_WHITE : GxEPD_BLACK; }
|
||||
// 1:1 MSB-first bitmap draw, no virtual block scaling
|
||||
void drawXbmRaw(int16_t x, int16_t y, const uint8_t* bits, int w, int h, uint16_t color);
|
||||
// Rounded-rect border band, `thickness` px wide, with 1-bit dither shading:
|
||||
// shade 0 = solid, 1 = dark grey (~75%), 2 = light grey (50% checker)
|
||||
void drawRoundRectShadedRaw(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint8_t thickness, uint8_t shade, uint16_t color);
|
||||
// drawTextRaw plus frame-CRC tracking so changed text triggers a refresh
|
||||
void drawTextRawTracked(int16_t x, int16_t y, const char* text, uint16_t color);
|
||||
// Style-honouring raw text: Classic uses the built-in 5x7, Noto/Montserrat
|
||||
// use their 7pt faces. `yTop` is the top of the text box (custom GFX fonts
|
||||
// position by baseline internally; this compensates). CRC tracked.
|
||||
int16_t measureTextRawStyled(const char* text);
|
||||
void drawTextRawStyled(int16_t x, int16_t yTop, const char* text, uint16_t color);
|
||||
|
||||
// Force endFrame() to push to display even if CRC unchanged
|
||||
// (needed because drawPixelRaw bypasses CRC tracking)
|
||||
void invalidateFrameCRC() { last_display_crc_value = 0; }
|
||||
|
||||
Reference in New Issue
Block a user