mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 10:51:57 +00:00
feat(eink): Text size and Icon size settings for big panels
On an 800x480 panel the doubled 6x9 font and the status bar's icons are tiny. Two opt-in build flags, each also a setting under Settings > Display: - EINK_FONT_SCALE=<n>: the landscape font's pixel multiplier (stock 2). Settings > Display > Text size (1x-5x) overrides it at run time (DisplayDriver::setUiScale -> EinkGfxDisplay re-lays out). - STATUS_ICON_SCALE=<n>: Home's status icons, battery and page-icon row drawn at n x their pixel grid, the bar growing to fit. 0 = Auto, 1.5x the text's pixel scale. Settings > Display > Icon size (Auto, 1x-8x). Both live in their own /ui_scale file, so NodePrefs is untouched. Icon size appears only where STATUS_ICON_SCALE is set; Text size appears on every e-ink build with the full-refresh setting, and builds that set neither flag draw as before until it is changed. The GDEQ0426T82 env defaults to 4x text with Auto icons. Tested in the native sim at 800x480. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxAZVAbhhYr18Dd4guTQM1
This commit is contained in:
@@ -337,8 +337,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() {
|
||||
@@ -724,8 +724,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;
|
||||
@@ -733,17 +736,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.
|
||||
@@ -809,7 +812,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;
|
||||
@@ -1240,7 +1244,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();
|
||||
@@ -1476,8 +1480,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
|
||||
|
||||
@@ -1492,6 +1496,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)];
|
||||
@@ -1517,10 +1522,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1535,7 +1540,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
|
||||
@@ -1560,7 +1565,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);
|
||||
@@ -2042,6 +2047,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
applyBrightness();
|
||||
applyRotation();
|
||||
applyFullRefreshInterval();
|
||||
loadUiScale(); // 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();
|
||||
@@ -4047,6 +4053,59 @@ void UITask::applyFullRefreshInterval() {
|
||||
}
|
||||
}
|
||||
|
||||
// Text size and Icon size (Settings > Display), in their own /ui_scale file --
|
||||
// [text pixel scale (0 = build default), icon scale (0 = Auto)] -- so the
|
||||
// NodePrefs schema is untouched.
|
||||
static const char* UI_SCALE_FILE = "/ui_scale";
|
||||
static const uint8_t UI_TEXT_SCALE_MAX = 5;
|
||||
|
||||
void UITask::loadUiScale() {
|
||||
uint8_t b[2] = { 0, statusIconScaleSetting() };
|
||||
DataStore* ds = the_mesh.getDataStore();
|
||||
if (ds) {
|
||||
File f = ds->openRead(UI_SCALE_FILE);
|
||||
if (f) {
|
||||
f.read(b, 2);
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
_ui_font_scale = b[0] <= UI_TEXT_SCALE_MAX ? b[0] : 0;
|
||||
if (b[1] <= STATUS_ICON_SCALE_MAX) statusIconScaleSetting() = b[1];
|
||||
if (_display != NULL && _ui_font_scale) _display->setUiScale(_ui_font_scale);
|
||||
}
|
||||
|
||||
void UITask::saveUiScale() {
|
||||
DataStore* ds = the_mesh.getDataStore();
|
||||
if (!ds) return;
|
||||
File f = ds->openWrite(UI_SCALE_FILE);
|
||||
if (f) {
|
||||
uint8_t b[2] = { _ui_font_scale, statusIconScaleSetting() };
|
||||
f.write(b, 2);
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t UITask::getUiFontScale() const {
|
||||
return _display != NULL ? (uint8_t)_display->pixelScale() : 1;
|
||||
}
|
||||
|
||||
void UITask::setUiFontScale(uint8_t s) {
|
||||
if (s < 1 || s > UI_TEXT_SCALE_MAX) s = 1;
|
||||
_ui_font_scale = s;
|
||||
if (_display != NULL) _display->setUiScale(s);
|
||||
_next_refresh = 0;
|
||||
saveUiScale();
|
||||
}
|
||||
|
||||
uint8_t UITask::getStatusIconScale() const { return statusIconScaleSetting(); }
|
||||
|
||||
void UITask::setStatusIconScale(uint8_t s) {
|
||||
if (s > STATUS_ICON_SCALE_MAX) s = 0;
|
||||
statusIconScaleSetting() = s;
|
||||
_next_refresh = 0; // the frame differs, so it's redrawn
|
||||
saveUiScale();
|
||||
}
|
||||
|
||||
void UITask::applySoundPrefs() {
|
||||
#ifdef PIN_BUZZER
|
||||
if (_node_prefs) setBuzzerVolumeLevel(_node_prefs->buzzer_volume); // with a sample at the new level
|
||||
|
||||
Reference in New Issue
Block a user