mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
Merge pull request #46 from pmow/feature/eink-ui-scale
GDEQ0426T82 4.26" panel (#42), bulk SPI writes (#43), Text size and Icon size for big panels # Conflicts: # examples/companion_radio/DataStore.cpp # examples/companion_radio/NodePrefs.h
This commit is contained in:
@@ -335,6 +335,11 @@ static void sanitize(NodePrefs& p) {
|
||||
flag(p.ble_off, 0);
|
||||
if (p.msg_alert > NodePrefs::MSG_ALERT_OFF) p.msg_alert = NodePrefs::MSG_ALERT_NORMAL;
|
||||
if (p.advert_loc_prec >= NodePrefs::ADV_LOC_PREC_COUNT) p.advert_loc_prec = 0;
|
||||
if (p.ui_text_scale > NodePrefs::UI_TEXT_SCALE_MAX) p.ui_text_scale = 0;
|
||||
#ifdef EINK_FONT_SCALE // the Text size row shows the scale in use: start at the build's
|
||||
if (p.ui_text_scale == 0) p.ui_text_scale = EINK_FONT_SCALE;
|
||||
#endif
|
||||
if (p.ui_icon_scale > NodePrefs::UI_ICON_SCALE_MAX) p.ui_icon_scale = 0;
|
||||
if (!p.ch_scope_follow) {
|
||||
for (uint8_t i = 0; i < NodePrefs::MAX_SCOPED_CHANNELS; i++)
|
||||
if (p.ch_scope_idx[i] == 0) p.ch_scope_idx[i] = NodePrefs::CH_SCOPE_DEFAULT;
|
||||
|
||||
@@ -544,6 +544,15 @@ struct NodePrefs { // persisted to file
|
||||
static const uint8_t ADV_LOC_PREC_COUNT = 4;
|
||||
uint8_t advert_loc_prec;
|
||||
|
||||
// ── Text and icon size (ui-new, big e-ink panels) ──────────────────────
|
||||
// Settings > Display, only on builds that opt in (EINK_FONT_SCALE /
|
||||
// STATUS_ICON_SCALE). ui_text_scale: the font's pixel multiplier, 1-5; 0 is
|
||||
// the build's own (DataStore sanitize sets EINK_FONT_SCALE there).
|
||||
// ui_icon_scale: Home's status icons, 0 = Auto (1.5x the text), else 1-8.
|
||||
static const uint8_t UI_TEXT_SCALE_MAX = 5, UI_ICON_SCALE_MAX = 8;
|
||||
uint8_t ui_text_scale;
|
||||
uint8_t ui_icon_scale;
|
||||
|
||||
// Single source of truth for the live-share option tables (shared by the Map
|
||||
// UI labels and the auto-send engine in UITask).
|
||||
static const uint8_t LOC_SHARE_MOVE_COUNT = 4;
|
||||
|
||||
@@ -24,6 +24,7 @@ static const Key KEYS[] = {
|
||||
K("contacts.expiry", contact_expiry_idx), K("contacts.fav.first", fav_sort), K("dm.resend", dm_resend_count),
|
||||
K("display.brightness", display_brightness), K("display.off", auto_off_secs), K("display.wake", msg_wake),
|
||||
K("display.alert", msg_alert), K("lock", auto_lock), K("lock.clock", lock_compact),
|
||||
K("display.text.size", ui_text_scale), K("display.icon.size", ui_icon_scale),
|
||||
K("batt.display", batt_display_mode), K("batt.shutdown", low_batt_mv), K("gps.interval", gps_interval),
|
||||
K("clock.12h", clock_12h), K("clock.seconds", clock_hide_seconds), K("units.imperial", units_imperial),
|
||||
K("sound.volume", buzzer_volume), K("quiet", quiet_hours), K("quiet.from", quiet_from), K("quiet.until", quiet_to),
|
||||
|
||||
@@ -144,6 +144,15 @@ static void optMsgAlert(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||
static const char* const L[] = { "Normal", "Compact", "Off" };
|
||||
snprintf(b, n, "%s", L[v < 3 ? v : 0]);
|
||||
}
|
||||
#ifdef EINK_FONT_SCALE
|
||||
static const int32_t TEXT_SCALE[] = { 1, 2, 3, 4, 5 };
|
||||
static void optTextScale(uint8_t v, char* b, int n, const NodePrefs&) { snprintf(b, n, "%ldx", (long)TEXT_SCALE[v]); }
|
||||
#endif
|
||||
#ifdef STATUS_ICON_SCALE
|
||||
static void optIconScale(uint8_t v, char* b, int n, const NodePrefs&) {
|
||||
if (v == 0) snprintf(b, n, "Auto"); else snprintf(b, n, "%ux", (unsigned)v);
|
||||
}
|
||||
#endif
|
||||
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&) {
|
||||
snprintf(b, n, "%s", NodePrefs::contactExpiryLabel(v));
|
||||
@@ -247,6 +256,15 @@ static const Setting ALL[] = {
|
||||
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),
|
||||
// Only the builds that ask for them (big e-ink panels): each screen elsewhere
|
||||
// is laid out for one cell size.
|
||||
#ifdef EINK_FONT_SCALE
|
||||
MAP("Text size", "Bigger letters on a big panel", SEC_DISPLAY, ui_text_scale, TEXT_SCALE, optTextScale, applyDisplay),
|
||||
#endif
|
||||
#ifdef STATUS_ICON_SCALE
|
||||
IDX("Icon size", "Home's status icons", SEC_DISPLAY, ui_icon_scale, NodePrefs::UI_ICON_SCALE_MAX + 1,
|
||||
optIconScale, applyDisplay),
|
||||
#endif
|
||||
|
||||
MAP("Battery shutdown", "Power off below this voltage", SEC_POWER, low_batt_mv, LOW_BATT, optLowBatt, nullptr),
|
||||
MAP("GPS power saving", "Sleep between fixes", SEC_POWER, gps_interval, GPS_DUTY, optGpsDuty, applyGpsDuty),
|
||||
|
||||
@@ -352,8 +352,8 @@ class HomeScreen : public UIScreen {
|
||||
|
||||
// First row under the header and the page-icon row (see render()).
|
||||
static int contentTop(DisplayDriver& d) {
|
||||
const int pg_half = (PAGE_ICON_PX * miniIconScale(d) + 1) / 2;
|
||||
return d.getLineHeight() + 2 * pg_half + 4;
|
||||
const int pg_half = (PAGE_ICON_PX * statusIconScale(d) + 1) / 2;
|
||||
return statusBarH(d) + 2 * pg_half + 4;
|
||||
}
|
||||
// 0..1 eased progress of the running slide, or -1 when none.
|
||||
float slideProgress() {
|
||||
@@ -739,8 +739,11 @@ class HomeScreen : public UIScreen {
|
||||
|
||||
const int lh = display.getLineHeight();
|
||||
const int cw = display.getCharWidth();
|
||||
const int ind = cw + 2; // single-char indicator width
|
||||
const int ind_h = display.isSingleFont() ? lh - 2 : lh;
|
||||
const int sis = statusIconScale(display); // STATUS_ICON_SCALE, or the font's
|
||||
const int bar_h = statusBarH(display);
|
||||
const int ind = STATUS_ICONS_SCALED ? 7 * sis + 2 : cw + 2; // indicator slot width
|
||||
const int ind_h = (bar_h > lh) ? bar_h - 2 : (display.isSingleFont() ? lh - 2 : lh);
|
||||
const int text_y = (bar_h > lh) ? (bar_h - lh) / 2 : 0; // % / V text centred in a tall bar
|
||||
const int ind_gap = display.pixelScale() > 1 ? 3 : 1; // gap between indicator boxes
|
||||
|
||||
int battLeftX;
|
||||
@@ -748,17 +751,17 @@ class HomeScreen : public UIScreen {
|
||||
char buf[6];
|
||||
snprintf(buf, sizeof(buf),"%d%%", pct);
|
||||
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
||||
display.setCursor(battLeftX, 0);
|
||||
display.setCursor(battLeftX, text_y);
|
||||
display.print(buf);
|
||||
} else if (mode == battery::VOLTAGE) {
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof(buf),"%u.%02uV", batteryMilliVolts / 1000, (batteryMilliVolts % 1000) / 10);
|
||||
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
||||
display.setCursor(battLeftX, 0);
|
||||
display.setCursor(battLeftX, text_y);
|
||||
display.print(buf);
|
||||
} else { // icon — scales with lh, same box height as the status icons beside it (ind_h)
|
||||
const int iconH = ind_h;
|
||||
const int iconW = lh * 2;
|
||||
const int iconW = (bar_h > lh) ? ind_h * 2 : lh * 2;
|
||||
const int bm = display.pixelScale() > 1 ? 3 : 2; // inner margin: 3px on a doubled panel, else 2px
|
||||
battLeftX = display.width() - iconW - 3;
|
||||
// The outline with its corner pixels left out, softly rounded.
|
||||
@@ -824,7 +827,8 @@ class HomeScreen : public UIScreen {
|
||||
if (!s.active) continue;
|
||||
int ix = x - ind - ind_gap;
|
||||
if (ix < name_min) break; // out of room — drop this + all lower priority
|
||||
drawSlotIcon(display, ix, ind, ind_h, *s.icon);
|
||||
if (STATUS_ICONS_SCALED) drawSlotIconScaled(display, ix, ind, ind_h, *s.icon, sis);
|
||||
else drawSlotIcon(display, ix, ind, ind_h, *s.icon);
|
||||
x = ix;
|
||||
}
|
||||
return x;
|
||||
@@ -1255,7 +1259,7 @@ public:
|
||||
const int W = d.width(), H = d.height();
|
||||
d.setColor(DisplayDriver::LIGHT);
|
||||
d.setTextSize(1);
|
||||
const int lh = d.getLineHeight(), top = lh + 3;
|
||||
const int lh = d.getLineHeight(), top = statusBarH(d) + 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();
|
||||
@@ -1491,8 +1495,8 @@ public:
|
||||
// Page-indicator row: small page icons (PAGE_ICON_PX) replace the old dots. Centre and
|
||||
// gap scale with the font so the band clears the header above and content
|
||||
// below (identical to the old lh+4 / +6 dots layout at 1x).
|
||||
const int pg_half = (PAGE_ICON_PX * miniIconScale(display) + 1) / 2;
|
||||
const int dots_y = lh + pg_half + 1; // icon-row centre, below the header
|
||||
const int pg_half = (PAGE_ICON_PX * statusIconScale(display) + 1) / 2; // page icons follow STATUS_ICON_SCALE
|
||||
const int dots_y = statusBarH(display) + pg_half + 1; // icon-row centre, below the header
|
||||
const int content_y = contentTop(display); // first content row, below the icons
|
||||
const float slide = slideProgress(); // -1, or how far a page turn has got
|
||||
|
||||
@@ -1507,6 +1511,7 @@ public:
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
|
||||
if (_page != LOCK) {
|
||||
const int bar_ty = (statusBarH(display) - lh) / 2; // text centred in a tall bar (STATUS_ICON_SCALE)
|
||||
// The time, once the clock is set (the node name before that): the
|
||||
// name is what others see, the time is what you glance at here.
|
||||
char filtered_name[sizeof(_node_prefs->node_name)];
|
||||
@@ -1532,10 +1537,10 @@ public:
|
||||
char pwr_buf[8];
|
||||
snprintf(pwr_buf, sizeof(pwr_buf), "%ddB", (int)radio_driver.getTxPower());
|
||||
int pwr_w = display.getTextWidth(pwr_buf);
|
||||
display.drawTextEllipsized(0, 0, rightEdge - 2 - pwr_w - 2, filtered_name);
|
||||
display.drawTextRightAlign(rightEdge - 2, 0, pwr_buf);
|
||||
display.drawTextEllipsized(0, bar_ty, rightEdge - 2 - pwr_w - 2, filtered_name);
|
||||
display.drawTextRightAlign(rightEdge - 2, bar_ty, pwr_buf);
|
||||
} else {
|
||||
display.drawTextEllipsized(0, 0, rightEdge - 2, filtered_name);
|
||||
display.drawTextEllipsized(0, bar_ty, rightEdge - 2, filtered_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1550,7 +1555,7 @@ public:
|
||||
int order[(int)Count]; int n = buildVisibleOrder(order);
|
||||
int curr_vis = 0;
|
||||
for (int i = 0; i < n; i++) if (order[i] == _page) { curr_vis = i; break; }
|
||||
const int s = miniIconScale(display);
|
||||
const int s = statusIconScale(display); // STATUS_ICON_SCALE, or the font's
|
||||
const int icon_w = PAGE_ICON_PX * s;
|
||||
int pitch = icon_w + 5 * s; // comfortable spacing
|
||||
if (n > 1) { // shrink to fit if many pages
|
||||
@@ -1575,7 +1580,7 @@ public:
|
||||
if (d2 <= pw - icon_w) display.setColor(DisplayDriver::DARK); // inside the pill
|
||||
else if (d2 < pw + icon_w) { x += pitch; continue; } // half under the gliding pill
|
||||
else display.setColor(DisplayDriver::LIGHT);
|
||||
if (ic) miniIconDrawCentered(display, x, dots_y, *ic);
|
||||
if (ic) miniIconDrawCenteredScaled(display, x, dots_y, *ic, s);
|
||||
x += pitch;
|
||||
}
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
@@ -2058,6 +2063,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
applyBrightness();
|
||||
applyRotation();
|
||||
applyFullRefreshInterval();
|
||||
applyUiScale(); // Settings > Display > Text size / Icon size
|
||||
applyAllGpioModes(); // restore persisted pin modes to hardware before any UI/bot use
|
||||
// Locked above (a PIN, or the cover closed) before Home existed to be told.
|
||||
if (_locked) syncLockToHome();
|
||||
@@ -4157,6 +4163,19 @@ void UITask::applyFullRefreshInterval() {
|
||||
}
|
||||
}
|
||||
|
||||
// Text size and Icon size (Settings > Display, NodePrefs ui_text_scale /
|
||||
// ui_icon_scale), on the builds that offer them. Elsewhere the fields stay 0:
|
||||
// the build's font scale and Auto icons, as stock.
|
||||
void UITask::applyUiScale() {
|
||||
if (!_node_prefs) return;
|
||||
#ifdef EINK_FONT_SCALE
|
||||
if (_display != NULL) _display->setUiScale(_node_prefs->ui_text_scale);
|
||||
#endif
|
||||
#if STATUS_ICONS_SCALED
|
||||
statusIconScaleSetting() = _node_prefs->ui_icon_scale;
|
||||
#endif
|
||||
}
|
||||
|
||||
void UITask::applySoundPrefs() {
|
||||
#ifdef PIN_BUZZER
|
||||
if (_node_prefs) setBuzzerVolumeLevel(_node_prefs->buzzer_volume); // with a sample at the new level
|
||||
|
||||
@@ -528,7 +528,7 @@ public:
|
||||
void applyAllGpioModes(); // boot-time restore from NodePrefs, called from begin()
|
||||
void applyBrightness();
|
||||
// Settings changed through the schema (ui-core/SettingsSchema.h).
|
||||
void applyDisplayPrefs() override { applyBrightness(); _next_refresh = 0; }
|
||||
void applyDisplayPrefs() override { applyBrightness(); applyUiScale(); _next_refresh = 0; }
|
||||
void applySoundPrefs() override;
|
||||
bool cliCommand(const char* command, char* reply, int n) override;
|
||||
void setBuzzerVolumeLevel(uint8_t level);
|
||||
@@ -543,6 +543,7 @@ public:
|
||||
bool savePrefsIfDirty(bool& dirty);
|
||||
void applyRotation();
|
||||
void applyFullRefreshInterval();
|
||||
void applyUiScale(); // Settings > Display > Text size / Icon size, from the prefs
|
||||
uint32_t autoOffMillis() const {
|
||||
if (!_node_prefs || _node_prefs->auto_off_secs == 0) return 0;
|
||||
return (uint32_t)_node_prefs->auto_off_secs * 1000UL;
|
||||
|
||||
@@ -91,6 +91,54 @@ inline void miniIconDrawCentered(DisplayDriver& d, int cx, int cy, const MiniIco
|
||||
miniIconDrawTop(d, cx - (ic.w * s) / 2, cy - (ic.h * s) / 2, ic);
|
||||
}
|
||||
|
||||
// Home status bar icon scale. Defining STATUS_ICON_SCALE (build flag) turns on
|
||||
// scalable status icons: the bar's icons, battery and the page-icon row below it
|
||||
// are drawn at n x their pixel grid, and the bar grows tall enough to hold them --
|
||||
// for big panels whose 1-line bar leaves them tiny -- and adds Settings > Display >
|
||||
// Icon size (NodePrefs::ui_icon_scale): 0 = Auto (1.5x the text's pixel scale),
|
||||
// else n. Unset: icons follow the font, as stock.
|
||||
#ifdef STATUS_ICON_SCALE
|
||||
#define STATUS_ICONS_SCALED 1
|
||||
#else
|
||||
#define STATUS_ICONS_SCALED 0
|
||||
#endif
|
||||
static constexpr int STATUS_ICON_ROWS = MINI_ICONS_LARGE == 2 ? 9 : 7; // tallest status icon
|
||||
// The Icon size setting (0 = Auto), copied from the prefs by UITask::applyUiScale().
|
||||
// One instance across translation units.
|
||||
inline uint8_t& statusIconScaleSetting() {
|
||||
static uint8_t v = 0;
|
||||
return v;
|
||||
}
|
||||
inline int statusIconScale(DisplayDriver& d) {
|
||||
if (!STATUS_ICONS_SCALED) return miniIconScale(d);
|
||||
int s = statusIconScaleSetting();
|
||||
if (s == 0) s = (3 * d.pixelScale() + 1) / 2; // Auto: 1.5x the text
|
||||
return s < 1 ? 1 : s;
|
||||
}
|
||||
// Height of the home status bar: one text line, or the scaled icons if taller.
|
||||
inline int statusBarH(DisplayDriver& d) {
|
||||
const int lh = d.getLineHeight();
|
||||
const int ih = STATUS_ICON_ROWS * statusIconScale(d) + 4;
|
||||
return (STATUS_ICONS_SCALED && ih > lh) ? ih : lh;
|
||||
}
|
||||
// miniIconDrawCentered at an explicit scale (the home page-icon row's).
|
||||
inline void miniIconDrawCenteredScaled(DisplayDriver& d, int cx, int cy, const MiniIcon& ic, int s) {
|
||||
const int x = cx - (ic.w * s) / 2, y = cy - (ic.h * s) / 2;
|
||||
for (int r = 0; r < ic.h; r++)
|
||||
for (int c = 0; c < ic.w; c++)
|
||||
if (ic.rows[r] & (1 << c)) d.fillRect(x + c * s, y + r * s, s, s);
|
||||
}
|
||||
// drawSlotIcon at an explicit scale (the status bar's).
|
||||
inline void drawSlotIconScaled(DisplayDriver& d, int x, int box_w, int box_h, const MiniIcon& ic, int s) {
|
||||
int ix = x + (box_w - ic.w * s) / 2;
|
||||
int iy = (box_h - ic.h * s) / 2;
|
||||
if (ix < x) ix = x;
|
||||
if (iy < 0) iy = 0;
|
||||
for (int r = 0; r < ic.h; r++)
|
||||
for (int c = 0; c < ic.w; c++)
|
||||
if (ic.rows[r] & (1 << c)) d.fillRect(ix + c * s, iy + r * s, s, s);
|
||||
}
|
||||
|
||||
// Centre a mini-icon inside the slot [x, 0, box_w, box_h] using the current ink
|
||||
// colour (no background). Centres on the box itself, not the text line, so the
|
||||
// glyph sits dead-centre regardless of font line height.
|
||||
|
||||
Reference in New Issue
Block a user