mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 19:02:00 +00:00
feat(l1): clock page and lock screen in the splash lettering
The Home clock page draws HH:MM in the boot splash's slanted digits, with a seconds bar, a centred date and the clock fields as columns. The lock screen keeps the status bar, puts the big clock under it and shows unread messages in a pill; the unlock hint appears for a few seconds after a key press on the lit screen (not the waking one). Settings > Display > Lock clock adds a compact look: a small clock with the date beside it and the clock fields as rows (NodePrefs lock_compact, schema 0xC0DE0033). 12-hour time sets AM / PM beside the digits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -646,6 +646,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
|||||||
// batt_curve_mv: any point out of range or not rising -> the built-in curve.
|
// batt_curve_mv: any point out of range or not rising -> the built-in curve.
|
||||||
rd(_prefs.batt_curve_mv, sizeof(_prefs.batt_curve_mv));
|
rd(_prefs.batt_curve_mv, sizeof(_prefs.batt_curve_mv));
|
||||||
if (!battery::validCurve(_prefs.batt_curve_mv)) memset(_prefs.batt_curve_mv, 0, sizeof(_prefs.batt_curve_mv));
|
if (!battery::validCurve(_prefs.batt_curve_mv)) memset(_prefs.batt_curve_mv, 0, sizeof(_prefs.batt_curve_mv));
|
||||||
|
rd(&_prefs.lock_compact, sizeof(_prefs.lock_compact));
|
||||||
|
if (_prefs.lock_compact > 1) _prefs.lock_compact = 0;
|
||||||
|
|
||||||
// Schema sentinel: bumped on layout changes. Mismatch means an older file
|
// Schema sentinel: bumped on layout changes. Mismatch means an older file
|
||||||
// (or a different schema); rd() and the clamps above already keep every
|
// (or a different schema); rd() and the clamps above already keep every
|
||||||
@@ -660,6 +662,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
|||||||
// file it holds its sentinel's bytes, which would read as a PIN nobody knows.
|
// file it holds its sentinel's bytes, which would read as a PIN nobody knows.
|
||||||
// 0xC0DE0031 → 0xC0DE0032: the battery curve appended; an older file
|
// 0xC0DE0031 → 0xC0DE0032: the battery curve appended; an older file
|
||||||
// leaves stray bytes in it, so the built-in curve.
|
// leaves stray bytes in it, so the built-in curve.
|
||||||
|
// 0xC0DE0032 → 0xC0DE0033: the lock screen look appended; big by default.
|
||||||
|
if (sentinel < 0xC0DE0033) _prefs.lock_compact = 0;
|
||||||
if (sentinel < 0xC0DE0032) memset(_prefs.batt_curve_mv, 0, sizeof(_prefs.batt_curve_mv));
|
if (sentinel < 0xC0DE0032) memset(_prefs.batt_curve_mv, 0, sizeof(_prefs.batt_curve_mv));
|
||||||
if (sentinel < 0xC0DE0031) {
|
if (sentinel < 0xC0DE0031) {
|
||||||
memset(_prefs.lock_screen_password, 0, sizeof(_prefs.lock_screen_password));
|
memset(_prefs.lock_screen_password, 0, sizeof(_prefs.lock_screen_password));
|
||||||
@@ -866,6 +870,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
|||||||
file.write((uint8_t *)_prefs.lock_screen_password, sizeof(_prefs.lock_screen_password));
|
file.write((uint8_t *)_prefs.lock_screen_password, sizeof(_prefs.lock_screen_password));
|
||||||
file.write((uint8_t *)_prefs.lock_screen_password_salt, sizeof(_prefs.lock_screen_password_salt));
|
file.write((uint8_t *)_prefs.lock_screen_password_salt, sizeof(_prefs.lock_screen_password_salt));
|
||||||
file.write((uint8_t *)_prefs.batt_curve_mv, sizeof(_prefs.batt_curve_mv));
|
file.write((uint8_t *)_prefs.batt_curve_mv, sizeof(_prefs.batt_curve_mv));
|
||||||
|
file.write((uint8_t *)&_prefs.lock_compact, sizeof(_prefs.lock_compact));
|
||||||
|
|
||||||
// Tail sentinel — must be last. See NodePrefs::SCHEMA_SENTINEL. Its write is
|
// Tail sentinel — must be last. See NodePrefs::SCHEMA_SENTINEL. Its write is
|
||||||
// the one we check: once the flash fills, writes return 0, so a good
|
// the one we check: once the flash fills, writes return 0, so a good
|
||||||
|
|||||||
@@ -563,6 +563,11 @@ struct NodePrefs { // persisted to file
|
|||||||
static const uint8_t BATT_CURVE_PTS = 11;
|
static const uint8_t BATT_CURVE_PTS = 11;
|
||||||
uint16_t batt_curve_mv[BATT_CURVE_PTS];
|
uint16_t batt_curve_mv[BATT_CURVE_PTS];
|
||||||
|
|
||||||
|
// ── Lock screen look (0xC0DE0033) ──────────────────────────────────────
|
||||||
|
// ui-new: 0 = big clock + date + unread pill, 1 = compact clock with the
|
||||||
|
// clock fields as rows below. The L2 has its own lock screen.
|
||||||
|
uint8_t lock_compact;
|
||||||
|
|
||||||
// Single source of truth for the live-share option tables (shared by the Map
|
// Single source of truth for the live-share option tables (shared by the Map
|
||||||
// UI labels and the auto-send engine in UITask).
|
// UI labels and the auto-send engine in UITask).
|
||||||
static const uint8_t LOC_SHARE_MOVE_COUNT = 4;
|
static const uint8_t LOC_SHARE_MOVE_COUNT = 4;
|
||||||
@@ -650,7 +655,7 @@ struct NodePrefs { // persisted to file
|
|||||||
// repeat_* fields) instead of at the tail, which shifted every field after
|
// repeat_* fields) instead of at the tail, which shifted every field after
|
||||||
// them by 25 bytes when loading an older file. Never released, but a dev
|
// them by 25 bytes when loading an older file. Never released, but a dev
|
||||||
// build wrote it, so the number must not be reused for anything else.
|
// build wrote it, so the number must not be reused for anything else.
|
||||||
static const uint32_t SCHEMA_SENTINEL = 0xC0DE0032;
|
static const uint32_t SCHEMA_SENTINEL = 0xC0DE0033;
|
||||||
|
|
||||||
// Bit-index for each home page. Used by page_order (entries store bit+1) and
|
// Bit-index for each home page. Used by page_order (entries store bit+1) and
|
||||||
// by home_pages_mask. Single source of truth — both HomeScreen::pageBit/bitToPage
|
// by home_pages_mask. Single source of truth — both HomeScreen::pageBit/bitToPage
|
||||||
@@ -837,7 +842,9 @@ static inline bool inQuietHours(const NodePrefs& p, uint32_t utc) {
|
|||||||
// lock_screen_password / _salt (0xC0DE0031) added 48 bytes -- sizeof 2880.
|
// lock_screen_password / _salt (0xC0DE0031) added 48 bytes -- sizeof 2880.
|
||||||
// batt_curve_mv[11] (0xC0DE0032) added 16 bytes, not 22 (6 went into tail
|
// batt_curve_mv[11] (0xC0DE0032) added 16 bytes, not 22 (6 went into tail
|
||||||
// padding) -- sizeof 2896, confirmed via real L1 (nRF52) and L2 (ESP32) builds.
|
// padding) -- sizeof 2896, confirmed via real L1 (nRF52) and L2 (ESP32) builds.
|
||||||
static_assert(sizeof(NodePrefs) == 2896,
|
// lock_compact (0xC0DE0033) added 8 bytes, not 1 (the struct rounds up to its
|
||||||
|
// alignment) -- sizeof 2904, confirmed via real L1 (nRF52) and L2 (ESP32) builds.
|
||||||
|
static_assert(sizeof(NodePrefs) == 2904,
|
||||||
"NodePrefs layout changed — sync DataStore save/load + clamp, bump "
|
"NodePrefs layout changed — sync DataStore save/load + clamp, bump "
|
||||||
"SCHEMA_SENTINEL, then update this size (see steps above).");
|
"SCHEMA_SENTINEL, then update this size (see steps above).");
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// The MeshCore wordmark and its lettering, shared by both boot splashes
|
// The MeshCore wordmark and its lettering, shared by both boot splashes
|
||||||
// (ui-new/UITask.cpp on the OLED/e-ink boards, ui-lvgl/Splash.h on the L2):
|
// (ui-new/UITask.cpp on the OLED/e-ink boards, ui-lvgl/Splash.h on the L2)
|
||||||
|
// and ui-new's big clock (Home clock page, lock screen):
|
||||||
// the 128x13 wordmark bitmap, its own letters, the extra characters drawn in
|
// the 128x13 wordmark bitmap, its own letters, the extra characters drawn in
|
||||||
// the same strokes (digits, '.', '-', v, l, d, space), and the Solo version /
|
// the same strokes (digits, '.', ':', '-', v, l, d, space), and the Solo version /
|
||||||
// build date strings both splashes show. Platform-free: callers plot pixels.
|
// build date strings both splashes show. Platform-free: callers plot pixels.
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -55,6 +56,7 @@ static const PixGlyph GLYPHS[] = {
|
|||||||
{ '8', { F10, F10, F10, B10, B10, F10, F10, F10, B10, B10, F10, F10, F10 } },
|
{ '8', { F10, F10, F10, B10, B10, F10, F10, F10, B10, B10, F10, F10, F10 } },
|
||||||
{ '9', { F10, F10, F10, B10, B10, F10, F10, F10, R10, R10, F10, F10, F10 } },
|
{ '9', { F10, F10, F10, B10, B10, F10, F10, F10, R10, R10, F10, F10, F10 } },
|
||||||
{ '.', { "....", "....", "....", "....", "....", "....", "....", "....", "....", "....", "###.", "###.", "###." } },
|
{ '.', { "....", "....", "....", "....", "....", "....", "....", "....", "....", "....", "###.", "###.", "###." } },
|
||||||
|
{ ':', { "....", "....", "....", "###.", "###.", "###.", "....", "....", "....", "###.", "###.", "###.", "...." } },
|
||||||
{ '-', { ".......", ".......", ".......", ".......", ".......", "#######", "#######", "#######",
|
{ '-', { ".......", ".......", ".......", ".......", ".......", "#######", "#######", "#######",
|
||||||
".......", ".......", ".......", ".......", "......." } },
|
".......", ".......", ".......", ".......", "......." } },
|
||||||
{ 'v', { "###.....###", "###.....###", "###.....###", "###.....###", "###.....###", "###.....###", "###.....###",
|
{ 'v', { "###.....###", "###.....###", "###.....###", "###.....###", "###.....###", "###.....###", "###.....###",
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ static void optBattDisplay(uint8_t v, char* b, int n, const NodePrefs&) {
|
|||||||
static const char* L[] = { "Icon", "Percent", "Voltage" };
|
static const char* L[] = { "Icon", "Percent", "Voltage" };
|
||||||
snprintf(b, n, "%s", L[v < 3 ? v : 0]);
|
snprintf(b, n, "%s", L[v < 3 ? v : 0]);
|
||||||
}
|
}
|
||||||
|
static void optLockLook(uint8_t v, char* b, int n, const NodePrefs&) { snprintf(b, n, "%s", v ? "Compact" : "Big"); }
|
||||||
static void optExpiry(uint8_t v, char* b, int n, const NodePrefs&) {
|
static void optExpiry(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||||
snprintf(b, n, "%s", NodePrefs::contactExpiryLabel(v));
|
snprintf(b, n, "%s", NodePrefs::contactExpiryLabel(v));
|
||||||
}
|
}
|
||||||
@@ -197,6 +198,7 @@ static const Setting ALL[] = {
|
|||||||
MAP("Wake on message", "Screen on for new messages", SEC_DISPLAY, msg_wake_screen_off, INVERTED,
|
MAP("Wake on message", "Screen on for new messages", SEC_DISPLAY, msg_wake_screen_off, INVERTED,
|
||||||
nullptr, nullptr),
|
nullptr, nullptr),
|
||||||
SW("Lock screen", "Slide to unlock after sleep", SEC_DISPLAY, auto_lock, nullptr),
|
SW("Lock screen", "Slide to unlock after sleep", SEC_DISPLAY, auto_lock, nullptr),
|
||||||
|
IDX("Lock clock", "Big, or compact with the clock fields", SEC_DISPLAY, lock_compact, 2, optLockLook, nullptr),
|
||||||
IDX("Battery display", "In the status bar", SEC_DISPLAY, batt_display_mode, 3, optBattDisplay, nullptr),
|
IDX("Battery display", "In the status bar", SEC_DISPLAY, batt_display_mode, 3, optBattDisplay, nullptr),
|
||||||
|
|
||||||
MAP("Battery shutdown", "Power off below this voltage", SEC_POWER, low_batt_mv, LOW_BATT, optLowBatt, nullptr),
|
MAP("Battery shutdown", "Power off below this voltage", SEC_POWER, low_batt_mv, LOW_BATT, optLowBatt, nullptr),
|
||||||
|
|||||||
@@ -195,18 +195,10 @@ static void fmtAlt(char* buf, int n, float meters, bool imperial) {
|
|||||||
// Voltage -> battery %, from the Core (top bar and dashboard Batt% alike).
|
// Voltage -> battery %, from the Core (top bar and dashboard Batt% alike).
|
||||||
static int battMvToPercent(int mv, int low_mv) { return battery::percent(mv, low_mv); }
|
static int battMvToPercent(int mv, int low_mv) { return battery::percent(mv, low_mv); }
|
||||||
|
|
||||||
// Render the time starting at top_y; returns the y just below the time block
|
// The time on a tall portrait panel (e-ink in portrait -- height > width): HH
|
||||||
// so the caller can flow the date / dashboard rows beneath it.
|
// and MM stacked on two lines in the huge built-in font (size 4, ~56 px tall)
|
||||||
//
|
// so the digits fill the narrow width. Returns the y just below it.
|
||||||
// On a tall portrait panel (e-ink in portrait — height > width) HH and MM are
|
static int drawClockTall(DisplayDriver& d, int top_y, const struct tm* ti, bool h12) {
|
||||||
// stacked on two lines in the huge built-in font (size 4, ~56 px tall) so the
|
|
||||||
// digits fill the narrow width. On wide panels (OLED, landscape e-ink) the
|
|
||||||
// classic single-line "HH:MM" at size 2 is kept.
|
|
||||||
static int drawClockTime(DisplayDriver& d, int top_y, const struct tm* ti,
|
|
||||||
bool h12, bool show_sec, bool alignCenter = false) {
|
|
||||||
const bool tall = d.height() > d.width(); // true only on portrait e-ink
|
|
||||||
|
|
||||||
if (tall) {
|
|
||||||
int hh = ti->tm_hour;
|
int hh = ti->tm_hour;
|
||||||
const char* ap = nullptr;
|
const char* ap = nullptr;
|
||||||
if (h12) { ap = (hh < 12) ? "AM" : "PM"; hh %= 12; if (hh == 0) hh = 12; }
|
if (h12) { ap = (hh < 12) ? "AM" : "PM"; hh %= 12; if (hh == 0) hh = 12; }
|
||||||
@@ -224,54 +216,80 @@ static int drawClockTime(DisplayDriver& d, int top_y, const struct tm* ti,
|
|||||||
// Centre on the visible width (minus that trailing column) instead.
|
// Centre on the visible width (minus that trailing column) instead.
|
||||||
const int trail = d.getCharWidth() / 6; // one built-in column at this size
|
const int trail = d.getCharWidth() / 6; // one built-in column at this size
|
||||||
auto drawBig = [&](const char* s, int yy) {
|
auto drawBig = [&](const char* s, int yy) {
|
||||||
if (alignCenter) {
|
d.setCursor(cx - ((int)d.getTextWidth(s) - trail) / 2, yy);
|
||||||
int w = (int)d.getTextWidth(s) - trail;
|
|
||||||
d.setCursor(cx - w / 2, yy);
|
|
||||||
d.print(s);
|
d.print(s);
|
||||||
} else {
|
|
||||||
d.setCursor(0, yy);
|
|
||||||
d.print(s);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
drawBig(hbuf, y); y += lhb + 2;
|
drawBig(hbuf, y); y += lhb + 2;
|
||||||
drawBig(mbuf, y); y += lhb + 2;
|
drawBig(mbuf, y); y += lhb + 2;
|
||||||
if (ap) {
|
if (ap) {
|
||||||
d.setTextSize(2);
|
d.setTextSize(2);
|
||||||
if (alignCenter) {
|
|
||||||
d.drawTextCentered(cx, y, ap);
|
d.drawTextCentered(cx, y, ap);
|
||||||
} else {
|
|
||||||
d.setCursor(0, y);
|
|
||||||
d.print(ap);
|
|
||||||
}
|
|
||||||
y += d.getLineHeight() + 1;
|
y += d.getLineHeight() + 1;
|
||||||
}
|
}
|
||||||
d.setTextSize(1);
|
d.setTextSize(1);
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wide layout: single inline line at size 2.
|
// The big clock on wide panels (Home clock page, lock screen): HH:MM in the
|
||||||
char buf[16];
|
// splash's slanted lettering (ui-core/Lettering.h) at `sc`, centred on cx. A
|
||||||
d.setTextSize(2);
|
// 12-hour clock drops the leading zero, closes the gaps up and sets AM / PM
|
||||||
const int lh2 = d.getLineHeight();
|
// beside the digits on their baseline -- "12:59 PM" still fits 128 px.
|
||||||
|
struct BigClock { int sc, h; };
|
||||||
|
static BigClock bigClockSize(const DisplayDriver& d) {
|
||||||
|
int sc = d.width() / 64;
|
||||||
|
if (sc > d.height() / 32) sc = d.height() / 32;
|
||||||
|
if (sc < 1) sc = 1;
|
||||||
|
return { sc, lettering::LOGO_H * sc };
|
||||||
|
}
|
||||||
|
static void drawLettering(DisplayDriver& d, int x, int y, const char* t, int sc, int gap) {
|
||||||
|
for (const char* p = t; *p; p++) {
|
||||||
|
const int cw = lettering::charW(*p);
|
||||||
|
if (cw < 0) continue;
|
||||||
|
for (int r = 0; r < lettering::LOGO_H; r++)
|
||||||
|
for (int col = 0; col < cw; col++) {
|
||||||
|
if (!lettering::inked(*p, col, r)) continue;
|
||||||
|
int e = col;
|
||||||
|
while (e + 1 < cw && lettering::inked(*p, e + 1, r)) e++;
|
||||||
|
d.fillRect(x + col * sc, y + r * sc, (e - col + 1) * sc, sc);
|
||||||
|
col = e;
|
||||||
|
}
|
||||||
|
x += cw * sc + gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The big clock's text, gaps and AM / PM, measured: its full width (w), the
|
||||||
|
// digits' (dw). draw() puts its left edge at x.
|
||||||
|
struct BigClockText {
|
||||||
|
char t[8];
|
||||||
|
const char* ap = nullptr;
|
||||||
|
int sc, gap, dw, w;
|
||||||
|
BigClockText(DisplayDriver& d, const struct tm* ti, bool h12, int scale) : sc(scale), gap(scale + 1) {
|
||||||
if (h12) {
|
if (h12) {
|
||||||
int hh = ti->tm_hour % 12; if (hh == 0) hh = 12;
|
int hh = ti->tm_hour % 12; if (hh == 0) hh = 12;
|
||||||
const char* ap = (ti->tm_hour < 12) ? "AM" : "PM";
|
snprintf(t, sizeof(t), "%d:%02d", hh, ti->tm_min);
|
||||||
if (show_sec) snprintf(buf, sizeof(buf), "%d:%02d:%02d%s", hh, ti->tm_min, ti->tm_sec, ap);
|
ap = ti->tm_hour < 12 ? "AM" : "PM";
|
||||||
else snprintf(buf, sizeof(buf), "%d:%02d %s", hh, ti->tm_min, ap);
|
gap = sc / 2;
|
||||||
} else {
|
} else {
|
||||||
if (show_sec) snprintf(buf, sizeof(buf), "%02d:%02d:%02d", ti->tm_hour, ti->tm_min, ti->tm_sec);
|
snprintf(t, sizeof(t), "%02d:%02d", ti->tm_hour, ti->tm_min);
|
||||||
else snprintf(buf, sizeof(buf), "%02d:%02d", ti->tm_hour, ti->tm_min);
|
|
||||||
}
|
|
||||||
if (alignCenter) {
|
|
||||||
d.drawTextCentered(d.width() / 2, top_y, buf);
|
|
||||||
} else {
|
|
||||||
d.setCursor(0, top_y);
|
|
||||||
d.print(buf);
|
|
||||||
}
|
}
|
||||||
|
dw = lettering::textW(t, 0) * sc + gap * ((int)strlen(t) - 1);
|
||||||
d.setTextSize(1);
|
d.setTextSize(1);
|
||||||
return top_y + lh2 + 2;
|
w = dw + (ap ? (int)d.getTextWidth(ap) + 2 * sc : 0);
|
||||||
|
}
|
||||||
|
void draw(DisplayDriver& d, int x, int y) const {
|
||||||
|
drawLettering(d, x, y, t, sc, gap);
|
||||||
|
if (!ap) return;
|
||||||
|
d.setCursor(x + dw + 2 * sc, y + lettering::LOGO_H * sc - d.getLineHeight() + 1);
|
||||||
|
d.print(ap);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static void drawBigClock(DisplayDriver& d, int cx, int y, const struct tm* ti, bool h12, int sc) {
|
||||||
|
BigClockText bt(d, ti, h12, sc);
|
||||||
|
bt.draw(d, cx - bt.w / 2, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char* const WDAY[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
|
||||||
|
static const char* const MON[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
|
||||||
|
|
||||||
// ── HomeScreen ────────────────────────────────────────────────────────────────
|
// ── HomeScreen ────────────────────────────────────────────────────────────────
|
||||||
// Forward declaration to be able to call formatDashVal from HomeScreen::render()
|
// Forward declaration to be able to call formatDashVal from HomeScreen::render()
|
||||||
static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_mv, uint16_t low_batt_mv,
|
static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_mv, uint16_t low_batt_mv,
|
||||||
@@ -1029,6 +1047,169 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The clock fields set in Settings (dashboard_fields), in order; their count.
|
||||||
|
int clockFields(uint8_t* out) {
|
||||||
|
int n = 0;
|
||||||
|
if (_node_prefs)
|
||||||
|
for (int k = 0; k < 3; k++)
|
||||||
|
if (_node_prefs->dashboard_fields[k] != telemetry::NONE && _node_prefs->dashboard_fields[k] < telemetry::COUNT)
|
||||||
|
out[n++] = _node_prefs->dashboard_fields[k];
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
// One field's value without its noun (the label beside it names it); "-" when there is none.
|
||||||
|
void clockFieldValue(uint8_t f, char* val, int n) {
|
||||||
|
formatDashVal(f, val, n, _task->getBattMilliVolts(), _node_prefs->low_batt_mv,
|
||||||
|
_task->getDMUnreadTotal() + _task->getChannelUnreadCount() + _task->getRoomUnreadCount(),
|
||||||
|
_task->getAnyUnreadOverflow(), _node_prefs->units_imperial, &sensors_lpp, false);
|
||||||
|
if (!val[0]) snprintf(val, n, "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Clock page: the big clock, a seconds bar under it, the date, and the
|
||||||
|
// clock fields (Settings) as columns along the bottom, name over value.
|
||||||
|
// Without fields the clock and date sit in the middle.
|
||||||
|
void drawClockPage(DisplayDriver& d, uint32_t unix_ts) {
|
||||||
|
struct tm ti;
|
||||||
|
localTm(unix_ts, _node_prefs ? _node_prefs->tz_offset_hours : 0, ti);
|
||||||
|
const bool h12 = _node_prefs && _node_prefs->clock_12h;
|
||||||
|
const bool show_sec = !Features::IS_EINK && (!_node_prefs || !_node_prefs->clock_hide_seconds);
|
||||||
|
const int W = d.width(), H = d.height();
|
||||||
|
d.setColor(DisplayDriver::LIGHT);
|
||||||
|
d.setTextSize(1);
|
||||||
|
const int lh = d.getLineHeight();
|
||||||
|
|
||||||
|
uint8_t fields[3];
|
||||||
|
const int nf = clockFields(fields);
|
||||||
|
|
||||||
|
int date_y;
|
||||||
|
if (d.height() > d.width()) { // portrait e-ink: stacked digits
|
||||||
|
date_y = drawClockTall(d, 0, &ti, h12);
|
||||||
|
} else {
|
||||||
|
const BigClock bc = bigClockSize(d);
|
||||||
|
const int bar = 3 * bc.sc / 2; // the seconds bar and its gaps
|
||||||
|
const int block = bc.h + bar + 1 + lh;
|
||||||
|
const int y = nf ? bc.sc : (H - block) / 2;
|
||||||
|
drawBigClock(d, W / 2, y, &ti, h12, bc.sc);
|
||||||
|
const int bar_y = y + bc.h + bar / 2 + 1;
|
||||||
|
if (show_sec) { // fills over the minute: dotted track, solid part
|
||||||
|
const int x0 = W / 8, bw = W - 2 * x0, fill = bw * ti.tm_sec / 60;
|
||||||
|
d.fillRect(x0, bar_y, fill, 1);
|
||||||
|
for (int x = x0 + fill + (fill & 1); x < x0 + bw; x += 2) d.fillRect(x, bar_y, 1, 1);
|
||||||
|
}
|
||||||
|
// Alarm armed: the bell at the bar's left end (the status bar, which
|
||||||
|
// shows it everywhere else, is hidden on this page).
|
||||||
|
if (_node_prefs && _node_prefs->alarm_on) miniIconDrawTop(d, W / 32, bar_y - 2, ICON_ALARM);
|
||||||
|
date_y = y + bc.h + bar + 1;
|
||||||
|
}
|
||||||
|
char buf[24];
|
||||||
|
snprintf(buf, sizeof(buf), "%s %d %s %d", WDAY[ti.tm_wday], ti.tm_mday, MON[ti.tm_mon], 1900 + ti.tm_year);
|
||||||
|
d.drawTextCentered(W / 2, date_y, buf);
|
||||||
|
|
||||||
|
if (nf == 0) return;
|
||||||
|
refresh_sensors();
|
||||||
|
const int val_y = H - lh, lab_y = val_y - lh - 1, cw = W / nf;
|
||||||
|
for (int k = 0; k < nf; k++) {
|
||||||
|
char val[20];
|
||||||
|
clockFieldValue(fields[k], val, sizeof(val));
|
||||||
|
const int x0 = k * cw;
|
||||||
|
auto cell = [&](int yy, const char* t) {
|
||||||
|
if ((int)d.getTextWidth(t) <= cw - 2) d.drawTextCentered(x0 + cw / 2, yy, t);
|
||||||
|
else d.drawTextEllipsized(x0 + 1, yy, cw - 2, t);
|
||||||
|
};
|
||||||
|
cell(lab_y, telemetry::LABEL[fields[k]]);
|
||||||
|
cell(val_y, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Lock screen: the status bar (drawn by render()), then one of two
|
||||||
|
// looks (Settings > Display > Lock clock):
|
||||||
|
// big -- the big clock, the date, unread messages in a pill;
|
||||||
|
// compact -- a small clock with the date beside it, the clock fields as
|
||||||
|
// rows below (plus unread messages when no field shows them).
|
||||||
|
// A key press on the lit screen (not the one that wakes it, so a glance
|
||||||
|
// shows the data) puts the unlock hint in a pill at the bottom for a few
|
||||||
|
// seconds (UITask::lockHintShown); e-ink keeps it up, it has no glance.
|
||||||
|
void drawLockPage(DisplayDriver& d) {
|
||||||
|
const int W = d.width(), H = d.height();
|
||||||
|
d.setColor(DisplayDriver::LIGHT);
|
||||||
|
d.setTextSize(1);
|
||||||
|
const int lh = d.getLineHeight(), top = lh + 3;
|
||||||
|
const bool hint = Features::IS_EINK || _task->lockHintShown();
|
||||||
|
const int pill_y = H - lh - 3; // the bottom pill, clear of what's above
|
||||||
|
const int unread = _task->getDMUnreadTotal() + _task->getChannelUnreadCount() + _task->getRoomUnreadCount();
|
||||||
|
const uint32_t unix_ts = _rtc->getCurrentTime();
|
||||||
|
const bool synced = unix_ts >= 1000000000UL;
|
||||||
|
struct tm ti;
|
||||||
|
if (synced) localTm(unix_ts, _node_prefs ? _node_prefs->tz_offset_hours : 0, ti);
|
||||||
|
const bool h12 = _node_prefs && _node_prefs->clock_12h;
|
||||||
|
const bool compact = _node_prefs && _node_prefs->lock_compact && W > H;
|
||||||
|
|
||||||
|
char pill[24] = "";
|
||||||
|
if (compact) {
|
||||||
|
const int m = W / 16, x0 = W / 32;
|
||||||
|
int sc = bigClockSize(d).sc / 2;
|
||||||
|
if (sc < 1) sc = 1;
|
||||||
|
const int ch = lettering::LOGO_H * sc;
|
||||||
|
if (!synced) {
|
||||||
|
d.setCursor(x0, top + (ch - lh + 1) / 2);
|
||||||
|
d.print("No time sync");
|
||||||
|
} else { // the date beside the clock, without the weekday if it won't fit
|
||||||
|
BigClockText bt(d, &ti, h12, sc);
|
||||||
|
bt.draw(d, x0, top);
|
||||||
|
char date[16];
|
||||||
|
snprintf(date, sizeof(date), "%s %d %s", WDAY[ti.tm_wday], ti.tm_mday, MON[ti.tm_mon]);
|
||||||
|
if (x0 + bt.w + lh / 2 + (int)d.getTextWidth(date) > W - x0)
|
||||||
|
snprintf(date, sizeof(date), "%d %s", ti.tm_mday, MON[ti.tm_mon]);
|
||||||
|
d.drawTextRightAlign(W - x0, top + (ch - lh + 1) / 2, date);
|
||||||
|
}
|
||||||
|
uint8_t rows[4];
|
||||||
|
int nr = clockFields(rows);
|
||||||
|
bool has_msgs = false;
|
||||||
|
for (int k = 0; k < nr; k++) if (rows[k] == telemetry::MSGS) has_msgs = true;
|
||||||
|
if (!has_msgs && unread > 0) rows[nr++] = telemetry::MSGS;
|
||||||
|
if (nr) refresh_sensors();
|
||||||
|
const int step = d.lineStep();
|
||||||
|
for (int k = 0, y = top + ch + 6; k < nr && y + lh <= H; k++, y += step) {
|
||||||
|
if (hint && y + lh >= pill_y) break; // the hint pill takes the bottom
|
||||||
|
char val[20];
|
||||||
|
clockFieldValue(rows[k], val, sizeof(val));
|
||||||
|
info::valueRow(d, y, telemetry::LABEL[rows[k]], val, false, m - 2, m);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!synced) {
|
||||||
|
d.drawTextCentered(W / 2, H / 2 - lh, "No time sync");
|
||||||
|
} else {
|
||||||
|
int date_y;
|
||||||
|
if (H > W) {
|
||||||
|
date_y = drawClockTall(d, top, &ti, h12);
|
||||||
|
} else {
|
||||||
|
const BigClock bc = bigClockSize(d);
|
||||||
|
drawBigClock(d, W / 2, top, &ti, h12, bc.sc);
|
||||||
|
date_y = top + bc.h + 3;
|
||||||
|
}
|
||||||
|
char buf[16];
|
||||||
|
snprintf(buf, sizeof(buf), "%s %d %s", WDAY[ti.tm_wday], ti.tm_mday, MON[ti.tm_mon]);
|
||||||
|
d.drawTextCentered(W / 2, date_y, buf);
|
||||||
|
}
|
||||||
|
if (!hint && unread > 0) snprintf(pill, sizeof(pill), "%d%s new", unread, _task->getAnyUnreadOverflow() ? "+" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hint) {
|
||||||
|
const int n = _task->lockSeqCount();
|
||||||
|
#if defined(CARDKB_I2C)
|
||||||
|
const char* first = _task->hasCardKB() ? "Back+3xEnter/Fn+Esc" : "Hold Back + 3xEnter";
|
||||||
|
#else
|
||||||
|
const char* first = "Hold Back + 3xEnter";
|
||||||
|
#endif
|
||||||
|
snprintf(pill, sizeof(pill), "%s", n == 0 ? first : n == 1 ? "Enter x2 more..." : "Enter x1 more...");
|
||||||
|
}
|
||||||
|
if (!pill[0]) return;
|
||||||
|
const int pw = d.getTextWidth(pill) + 6;
|
||||||
|
d.fillSoftRect((W - pw) / 2, pill_y, pw, lh + 3);
|
||||||
|
d.setColor(DisplayDriver::DARK);
|
||||||
|
d.drawTextCentered(W / 2, pill_y + 2, pill);
|
||||||
|
d.setColor(DisplayDriver::LIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
// The Status page: four tiles split by dotted rules, each one subject's
|
// The Status page: four tiles split by dotted rules, each one subject's
|
||||||
// headline (icon + main value) over a detail line. Enter opens the Status
|
// headline (icon + main value) over a detail line. Enter opens the Status
|
||||||
// screen with all of it.
|
// screen with all of it.
|
||||||
@@ -1130,18 +1311,9 @@ public:
|
|||||||
// Hidden on fullscreen pages (CLOCK).
|
// Hidden on fullscreen pages (CLOCK).
|
||||||
if (_page != CLOCK) {
|
if (_page != CLOCK) {
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
int lock_reserve = -1;
|
// The lock page has nothing on the left of the bar (its clock sits
|
||||||
if (_page == LOCK) {
|
// below it), so the status icons may use the whole row.
|
||||||
// The lock screen's own clock shares row 0 with this title bar --
|
const int lock_reserve = _page == LOCK ? 0 : -1;
|
||||||
// reserve its real footprint instead of the usual name_min, so the
|
|
||||||
// (already priority-ordered) status icons shed low-priority ones as
|
|
||||||
// needed and never draw over it. Built-in font is fixed-width, so a
|
|
||||||
// worst-case digit string measures this without the actual time.
|
|
||||||
bool tall = display.height() > display.width();
|
|
||||||
display.setTextSize(tall ? 4 : 2);
|
|
||||||
lock_reserve = display.getTextWidth(tall ? "88" : "88:88");
|
|
||||||
display.setTextSize(1);
|
|
||||||
}
|
|
||||||
int rightEdge = renderBatteryIndicator(display, _task->getBattMilliVolts(), lock_reserve);
|
int rightEdge = renderBatteryIndicator(display, _task->getBattMilliVolts(), lock_reserve);
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
|
|
||||||
@@ -1227,135 +1399,10 @@ public:
|
|||||||
display.drawTextCentered(display.width() / 2, mid_y + step, "Enable GPS or");
|
display.drawTextCentered(display.width() / 2, mid_y + step, "Enable GPS or");
|
||||||
display.drawTextCentered(display.width() / 2, mid_y + step * 2, "connect app");
|
display.drawTextCentered(display.width() / 2, mid_y + step * 2, "connect app");
|
||||||
} else {
|
} else {
|
||||||
struct tm lt;
|
drawClockPage(display, unix_ts);
|
||||||
localTm(unix_ts, _node_prefs ? _node_prefs->tz_offset_hours : 0, lt);
|
|
||||||
struct tm* ti = <
|
|
||||||
|
|
||||||
char buf[24];
|
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
|
||||||
bool show_sec = !Features::IS_EINK && (!_node_prefs || !_node_prefs->clock_hide_seconds);
|
|
||||||
bool h12 = _node_prefs && _node_prefs->clock_12h;
|
|
||||||
int date_y = drawClockTime(display, 0, ti, h12, show_sec, true);
|
|
||||||
|
|
||||||
display.setTextSize(1);
|
|
||||||
static const char* wd[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
|
|
||||||
static const char* mo[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
|
|
||||||
snprintf(buf, sizeof(buf),"%s %d %s %d", wd[ti->tm_wday], ti->tm_mday, mo[ti->tm_mon], 1900 + ti->tm_year);
|
|
||||||
display.setCursor(0, date_y);
|
|
||||||
display.print(buf);
|
|
||||||
|
|
||||||
// Alarm armed: a small bell in the top-left corner. The status bar (and
|
|
||||||
// its bell) is hidden on this page, so signal the armed alarm here. Just
|
|
||||||
// the glyph — no time text — so it stays clear of the centred clock
|
|
||||||
// digits (which can reach the corner when seconds are shown), matching
|
|
||||||
// the icon-only status-bar indicator. The exact time is in Clock Tools.
|
|
||||||
if (_node_prefs && _node_prefs->alarm_on) {
|
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
|
||||||
miniIconDrawTop(display, 0, 0, ICON_ALARM);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sep_y = date_y + lh + 1;
|
|
||||||
int dash0 = sep_y + display.sepH() + 2;
|
|
||||||
display.fillRect(0, sep_y, display.width(), display.sepH());
|
|
||||||
|
|
||||||
// dashboard data fields
|
|
||||||
if (_node_prefs) {
|
|
||||||
refresh_sensors();
|
|
||||||
const int FIELD_Y[3] = { dash0, dash0 + step, dash0 + step * 2 };
|
|
||||||
for (int fi = 0; fi < 3; fi++) {
|
|
||||||
uint8_t field = _node_prefs->dashboard_fields[fi];
|
|
||||||
if (field == telemetry::NONE) continue;
|
|
||||||
|
|
||||||
const char* label = telemetry::LABEL[field < telemetry::COUNT ? field : telemetry::NONE];
|
|
||||||
char val[20];
|
|
||||||
formatDashVal(field, val, sizeof(val), _task->getBattMilliVolts(), _node_prefs->low_batt_mv,
|
|
||||||
_task->getDMUnreadTotal() + _task->getChannelUnreadCount() + _task->getRoomUnreadCount(),
|
|
||||||
_task->getAnyUnreadOverflow(), _node_prefs->units_imperial, &sensors_lpp, false);
|
|
||||||
|
|
||||||
if (val[0] && label[0]) {
|
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
|
||||||
display.setCursor(0, FIELD_Y[fi]);
|
|
||||||
display.print(label);
|
|
||||||
int vw = display.getTextWidth(val);
|
|
||||||
display.setCursor(display.width() - vw - 1, FIELD_Y[fi]);
|
|
||||||
display.print(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (_page == HomePage::LOCK) {
|
} else if (_page == HomePage::LOCK) {
|
||||||
// Lock screen: clock + two dashboard spots + unlock-hint popup
|
drawLockPage(display);
|
||||||
uint32_t unix_ts = _rtc->getCurrentTime();
|
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
|
||||||
display.setTextSize(1);
|
|
||||||
if (unix_ts < 1000000000UL) {
|
|
||||||
display.drawTextCentered(display.width() / 2, display.height() / 2 - step, "No time sync");
|
|
||||||
} else {
|
|
||||||
struct tm lt;
|
|
||||||
localTm(unix_ts, _node_prefs ? _node_prefs->tz_offset_hours : 0, lt);
|
|
||||||
struct tm* ti = <
|
|
||||||
char buf[12];
|
|
||||||
bool h12 = _node_prefs && _node_prefs->clock_12h;
|
|
||||||
// Left-aligned on purpose: the rest of the top row is left for status icons.
|
|
||||||
int date_y = drawClockTime(display, 0, ti, h12, /*show_sec*/false);
|
|
||||||
display.setTextSize(1);
|
|
||||||
static const char* wd[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
|
|
||||||
static const char* mo[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
|
|
||||||
snprintf(buf, sizeof(buf),"%s %d %s", wd[ti->tm_wday], ti->tm_mday, mo[ti->tm_mon]);
|
|
||||||
display.setCursor(0, date_y);
|
|
||||||
display.print(buf);
|
|
||||||
|
|
||||||
// Two sensor values side by side (dashboard_fields[0] and [1])
|
|
||||||
if (_node_prefs) {
|
|
||||||
char v0[20] = "", v1[20] = "";
|
|
||||||
CayenneLPP* lpp_ptr = nullptr;
|
|
||||||
uint8_t f0 = _node_prefs->dashboard_fields[0], f1 = _node_prefs->dashboard_fields[1];
|
|
||||||
auto isLPP = [](uint8_t f) {
|
|
||||||
return f==telemetry::TEMP||f==telemetry::HUM||f==telemetry::PRES||f==telemetry::ALT||f==telemetry::LUX||f==telemetry::CO2;
|
|
||||||
};
|
|
||||||
if (isLPP(f0) || isLPP(f1)) {
|
|
||||||
sensors_lpp.reset(); sensors.querySensors(0xFF, sensors_lpp); lpp_ptr = &sensors_lpp;
|
|
||||||
}
|
|
||||||
bool show_msgs = f0 == telemetry::MSGS || f1 == telemetry::MSGS;
|
|
||||||
int unread = show_msgs
|
|
||||||
? _task->getDMUnreadTotal() + _task->getChannelUnreadCount() + _task->getRoomUnreadCount() : 0;
|
|
||||||
bool unread_overflow = show_msgs && _task->getAnyUnreadOverflow();
|
|
||||||
uint16_t batt_mv = _task->getBattMilliVolts();
|
|
||||||
formatDashVal(f0, v0, sizeof(v0), batt_mv, _node_prefs->low_batt_mv, unread, unread_overflow, _node_prefs->units_imperial, lpp_ptr, true);
|
|
||||||
formatDashVal(f1, v1, sizeof(v1), batt_mv, _node_prefs->low_batt_mv, unread, unread_overflow, _node_prefs->units_imperial, lpp_ptr, true);
|
|
||||||
if (v0[0] || v1[0]) {
|
|
||||||
int sv_y = date_y + step;
|
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
|
||||||
if (v0[0] && v1[0]) {
|
|
||||||
display.setCursor(0, sv_y);
|
|
||||||
display.print(v0);
|
|
||||||
int vw = display.getTextWidth(v1);
|
|
||||||
display.setCursor(display.width() - vw, sv_y);
|
|
||||||
display.print(v1);
|
|
||||||
} else {
|
|
||||||
const char* sv = v0[0] ? v0 : v1;
|
|
||||||
display.drawTextCentered(display.width() / 2, sv_y, sv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Unlock-hint popup at the bottom (like alert style)
|
|
||||||
display.setTextSize(1);
|
|
||||||
const int lk_lh = display.getLineHeight();
|
|
||||||
#if defined(CARDKB_I2C)
|
|
||||||
const char* hint = _task->lockSeqCount() == 0 ? (_task->hasCardKB() ? "Back+3xEnter/Fn+Esc" : "Hold Back + 3xEnter") :
|
|
||||||
_task->lockSeqCount() == 1 ? "Enter x2 more..." : "Enter x1 more...";
|
|
||||||
#else
|
|
||||||
const char* hint = _task->lockSeqCount() == 0 ? "Hold Back + 3xEnter" :
|
|
||||||
_task->lockSeqCount() == 1 ? "Enter x2 more..." : "Enter x1 more...";
|
|
||||||
#endif
|
|
||||||
const int p = 3;
|
|
||||||
const int hy = display.height() - lk_lh - p * 2;
|
|
||||||
const int hw = display.getTextWidth(hint);
|
|
||||||
const int hx = (display.width() - hw) / 2;
|
|
||||||
display.drawPanel(hx - p, hy - p, hw + p*2, lk_lh + p*2);
|
|
||||||
display.setCursor(hx, hy);
|
|
||||||
display.print(hint);
|
|
||||||
} else if (_page == HomePage::STATUS) {
|
} else if (_page == HomePage::STATUS) {
|
||||||
drawStatusTiles(display, content_y);
|
drawStatusTiles(display, content_y);
|
||||||
} else if (isQuickPage(_page) && !(_page == HomePage::SHUTDOWN && _shutdown_init)) {
|
} else if (isQuickPage(_page) && !(_page == HomePage::SHUTDOWN && _shutdown_init)) {
|
||||||
@@ -2998,7 +3045,7 @@ void UITask::loop() {
|
|||||||
} else {
|
} else {
|
||||||
_kq_head = _kq_tail = 0; // locked or no screen: eat all queued keys
|
_kq_head = _kq_tail = 0; // locked or no screen: eat all queued keys
|
||||||
// Locked: wake window is set only when display first turns on
|
// Locked: wake window is set only when display first turns on
|
||||||
if (_locked) _next_refresh = 0;
|
if (_locked) { _lock_hint_ms = millis(); _next_refresh = 0; } // the hint; not on the waking press, so a glance shows the data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ class UITask : public UITaskBase, public UiCoreHost {
|
|||||||
unsigned long _lock_wake_until; // when to blank screen again after locked wake (5s)
|
unsigned long _lock_wake_until; // when to blank screen again after locked wake (5s)
|
||||||
int _lock_seq_count; // Enter presses while Back held (lock/unlock sequence)
|
int _lock_seq_count; // Enter presses while Back held (lock/unlock sequence)
|
||||||
unsigned long _lock_seq_ms; // millis() of last lock-sequence press (for timeout)
|
unsigned long _lock_seq_ms; // millis() of last lock-sequence press (for timeout)
|
||||||
|
unsigned long _lock_hint_ms; // millis() of the last key press on the lock screen (0: none)
|
||||||
bool _lock_seq_used; // true = suppress next back_btn CLICK (post-sequence release)
|
bool _lock_seq_used; // true = suppress next back_btn CLICK (post-sequence release)
|
||||||
// True while the lock screen shows the on-screen keyboard and waits for submission
|
// True while the lock screen shows the on-screen keyboard and waits for submission
|
||||||
bool _unlock_kb = false;
|
bool _unlock_kb = false;
|
||||||
@@ -255,7 +256,7 @@ public:
|
|||||||
_batt_mv = 0;
|
_batt_mv = 0;
|
||||||
_locked = false;
|
_locked = false;
|
||||||
_lock_wake_until = 0;
|
_lock_wake_until = 0;
|
||||||
_lock_seq_count = 0; _lock_seq_ms = 0; _lock_seq_used = false;
|
_lock_seq_count = 0; _lock_seq_ms = 0; _lock_seq_used = false; _lock_hint_ms = 0;
|
||||||
_last_notif_ch_idx = -1;
|
_last_notif_ch_idx = -1;
|
||||||
_last_notif_dm_valid = false;
|
_last_notif_dm_valid = false;
|
||||||
memset(_last_notif_dm_prefix, 0, sizeof(_last_notif_dm_prefix));
|
memset(_last_notif_dm_prefix, 0, sizeof(_last_notif_dm_prefix));
|
||||||
@@ -420,6 +421,9 @@ public:
|
|||||||
// Lock-screen support: the HomeScreen LOCK page draws the unlock hint from
|
// Lock-screen support: the HomeScreen LOCK page draws the unlock hint from
|
||||||
// these (the platform combo is either "Back+3xEnter" or the CardKB nybble).
|
// these (the platform combo is either "Back+3xEnter" or the CardKB nybble).
|
||||||
int lockSeqCount() const { return _lock_seq_count; }
|
int lockSeqCount() const { return _lock_seq_count; }
|
||||||
|
// The lock screen shows the unlock hint for a few seconds after a key press
|
||||||
|
// (and all through the Back + 3x Enter sequence).
|
||||||
|
bool lockHintShown() const { return _lock_seq_count > 0 || (_lock_hint_ms && millis() - _lock_hint_ms < 3000); }
|
||||||
bool hasCardKB() const {
|
bool hasCardKB() const {
|
||||||
#if defined(CARDKB_I2C)
|
#if defined(CARDKB_I2C)
|
||||||
return _has_cardkb;
|
return _has_cardkb;
|
||||||
|
|||||||
Reference in New Issue
Block a user