From 1d8ca2546978999c8f8fe535cb09488f11198400 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Fri, 22 May 2026 18:34:20 +0200 Subject: [PATCH] fix: adaptive layout for e-ink landscape across all remaining screens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GxEPDDisplay: sz2 uses 4× scale on landscape (clock 32px vs body 16px) - UITask: clock date_y and splash date_y computed from lh2 after setTextSize(2) - UITask: battery/mute/BT/advert indicators scale with lh; muted uses text 'M' - UITask: hibernate hint split into two lines to prevent wrapping - ToolsScreen: full refactor — headerH, listStart, lineStep, getCharWidth - KeyboardWidget: all cell sizes computed from display metrics (cell_w, cell_h, spec_w) - RingtoneEditorScreen: cell_w = charWidth×2+6 (was hardcoded 18) - SettingsScreen: BRIGHTNESS hidden on e-ink; renderBar boxes scale with lh Co-Authored-By: Claude Sonnet 4.6 --- .../companion_radio/ui-new/KeyboardWidget.h | 42 +++++++------ .../ui-new/RingtoneEditorScreen.h | 17 ++--- .../companion_radio/ui-new/SettingsScreen.h | 15 ++++- examples/companion_radio/ui-new/ToolsScreen.h | 12 ++-- examples/companion_radio/ui-new/UITask.cpp | 63 +++++++++++-------- src/helpers/ui/GxEPDDisplay.cpp | 2 +- src/helpers/ui/GxEPDDisplay.h | 4 +- 7 files changed, 93 insertions(+), 62 deletions(-) diff --git a/examples/companion_radio/ui-new/KeyboardWidget.h b/examples/companion_radio/ui-new/KeyboardWidget.h index 8ea7334b..8cfbfd1d 100644 --- a/examples/companion_radio/ui-new/KeyboardWidget.h +++ b/examples/companion_radio/ui-new/KeyboardWidget.h @@ -15,12 +15,6 @@ static const int KB_ROWS_CHAR = 4; static const int KB_COLS_CHAR = 10; static const int KB_SPECIAL = 5; // [^] [Sp] [De] [{}] [OK] static const int KB_MAX_LEN = 139; -static const int KB_CELL_W = 12; // 10 cols × 12px = 120px of 128px display -static const int KB_CELL_H = 9; -static const int KB_TEXT_Y = 0; -static const int KB_SEP_Y = 9; -static const int KB_CHARS_Y = 11; -static const int KB_SPECIAL_Y = 48; static const int KB_PH_MAX = 12; // max placeholders in list static const int KB_PH_LEN = 9; // max placeholder string length incl. null @@ -64,33 +58,44 @@ struct KeyboardWidget { display.setTextSize(1); display.setColor(DisplayDriver::LIGHT); - // text preview: last 20 chars + cursor + const int lh = display.getLineHeight(); + const int cw = display.getCharWidth(); + const int cell_w = display.width() / KB_COLS_CHAR; + const int cell_h = lh + 1; + const int sep_y = lh + 1; + const int chars_y = sep_y + 2; + const int spec_y = chars_y + KB_ROWS_CHAR * cell_h; + const int spec_w = display.width() / KB_SPECIAL; + + // text preview: last N chars + cursor (fit within screen width) + int max_preview = display.width() / cw - 1; + if (max_preview > 30) max_preview = 30; const char* disp_start = buf; int disp_len = len; - if (disp_len > 20) { disp_start = buf + (disp_len - 20); disp_len = 20; } - char preview[24]; + if (disp_len > max_preview) { disp_start = buf + (disp_len - max_preview); disp_len = max_preview; } + char preview[32]; snprintf(preview, sizeof(preview), "%.*s_", disp_len, disp_start); - display.setCursor(0, KB_TEXT_Y); + display.setCursor(0, 0); display.print(preview); - display.fillRect(0, KB_SEP_Y, display.width(), 1); + display.fillRect(0, sep_y, display.width(), 1); // character grid for (int r = 0; r < KB_ROWS_CHAR; r++) { - int y = KB_CHARS_Y + r * KB_CELL_H; + int y = chars_y + r * cell_h; for (int c = 0; c < KB_COLS_CHAR; c++) { bool sel = (row == r && col == c); char ch = KB_CHARS[r][c]; if (caps && ch >= 'a' && ch <= 'z') ch = ch - 'a' + 'A'; char ch_buf[2] = { ch == ' ' ? '_' : ch, '\0' }; - int cx = c * KB_CELL_W; + int cx = c * cell_w; if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(cx, y - 1, KB_CELL_W - 1, KB_CELL_H); + display.fillRect(cx, y - 1, cell_w - 1, cell_h); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); } - display.setCursor(cx + 3, y); + display.setCursor(cx + (cell_w - cw) / 2, y); display.print(ch_buf); } } @@ -100,15 +105,16 @@ struct KeyboardWidget { for (int i = 0; i < KB_SPECIAL; i++) { bool sel = (row == KB_ROWS_CHAR && col == i); bool active = (i == 0 && caps); - int sx = i * 25; + int sx = i * spec_w; if (sel || active) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(sx, KB_SPECIAL_Y - 1, 24, KB_CELL_H); + display.fillRect(sx, spec_y - 1, spec_w - 1, cell_h); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); } - display.setCursor(sx + 1, KB_SPECIAL_Y); + int tw = display.getTextWidth(spec[i]); + display.setCursor(sx + (spec_w - tw) / 2, spec_y); display.print(spec[i]); display.setColor(DisplayDriver::LIGHT); } diff --git a/examples/companion_radio/ui-new/RingtoneEditorScreen.h b/examples/companion_radio/ui-new/RingtoneEditorScreen.h index 7026d277..66801e9a 100644 --- a/examples/companion_radio/ui-new/RingtoneEditorScreen.h +++ b/examples/companion_radio/ui-new/RingtoneEditorScreen.h @@ -7,7 +7,6 @@ class RingtoneEditorScreen : public UIScreen { NodePrefs* _prefs; static const int MAX_NOTES = 32; - static const int CELL_W = 18; static const int MENU_VISIBLE = 5; int _visible_notes = 7; // updated in render(); used by clampScroll() @@ -93,9 +92,11 @@ public: display.setColor(DisplayDriver::LIGHT); const int lh = display.getLineHeight(); + const int cw = display.getCharWidth(); + const int cell_w = cw * 2 + 6; // fits 2-char label with margin const int notes_y = display.listStart(); const int cell_h = lh + 6; - _visible_notes = display.width() / CELL_W; + _visible_notes = display.width() / cell_w; char hdr[32]; snprintf(hdr, sizeof(hdr), "M%d BPM:%u %d/%d", _slot + 1, BPM_OPTS[_bpm_idx], _len, MAX_NOTES); @@ -105,7 +106,7 @@ public: for (int i = 0; i < _visible_notes; i++) { int ni = _scroll + i; - int cx = i * CELL_W; + int cx = i * cell_w; bool sel = (ni == _cursor); if (ni < _len) { uint8_t pitch = notePitch(_notes[ni]); @@ -120,24 +121,24 @@ public: } if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(cx, notes_y, CELL_W - 1, cell_h); + display.fillRect(cx, notes_y, cell_w - 1, cell_h); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); - display.drawRect(cx, notes_y, CELL_W - 1, cell_h); + display.drawRect(cx, notes_y, cell_w - 1, cell_h); } - display.setCursor(cx + 3, notes_y + 3); + display.setCursor(cx + (cell_w - cw * 2) / 2, notes_y + 3); display.print(label); display.setColor(DisplayDriver::LIGHT); } else if (ni == _len && _len < MAX_NOTES) { if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(cx, notes_y, CELL_W - 1, cell_h); + display.fillRect(cx, notes_y, cell_w - 1, cell_h); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); } - display.setCursor(cx + 6, notes_y + 3); + display.setCursor(cx + (cell_w - cw) / 2, notes_y + 3); display.print("+"); display.setColor(DisplayDriver::LIGHT); } diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index 17f96473..8f390ee2 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -8,7 +8,9 @@ class SettingsScreen : public UIScreen { enum SettingItem { // Display section SECTION_DISPLAY, +#if !defined(EINK_DISPLAY_MODEL) BRIGHTNESS, +#endif AUTO_OFF, AUTO_LOCK, BATT_DISPLAY, @@ -85,7 +87,9 @@ class SettingsScreen : public UIScreen { } void renderBar(DisplayDriver& display, int x, int y, int value, int max_val) { - const int box_w = 7, box_h = 7, gap = 2; + const int box_h = display.getLineHeight() - 2; + const int box_w = box_h; + const int gap = 2; for (int i = 0; i < max_val; i++) { int bx = x + i * (box_w + gap); display.drawRect(bx, y, box_w, box_h); @@ -236,10 +240,13 @@ class SettingsScreen : public UIScreen { display.print(sel ? ">" : " "); display.setCursor(display.getCharWidth() + 2, y); +#if !defined(EINK_DISPLAY_MODEL) if (item == BRIGHTNESS) { display.print("Bright"); renderBar(display, display.valCol(), y, (p ? p->display_brightness : 2) + 1, 5); - } else if (item == BUZZER) { + } else +#endif + if (item == BUZZER) { display.print("Buzzer"); display.setCursor(display.valCol(), y); #ifdef PIN_BUZZER @@ -357,7 +364,7 @@ class SettingsScreen : public UIScreen { public: SettingsScreen(UITask* task) - : _task(task), _selected(BRIGHTNESS), _scroll(0), _visible(4), _dirty(false), _edit_slot(-1) {} + : _task(task), _selected(AUTO_OFF), _scroll(0), _visible(4), _dirty(false), _edit_slot(-1) {} void markClean() { _dirty = false; } @@ -441,12 +448,14 @@ public: bool left = (c == KEY_LEFT || c == KEY_PREV); bool enter = (c == KEY_ENTER); +#if !defined(EINK_DISPLAY_MODEL) if (_selected == BRIGHTNESS) { uint8_t lvl = _task->getBrightnessLevel(); if (right && lvl < 4) { _task->setBrightnessLevel(lvl + 1); _dirty = true; return true; } if (left && lvl > 0) { _task->setBrightnessLevel(lvl - 1); _dirty = true; return true; } return right || left; } +#endif if (_selected == BUZZER && (left || right || enter)) { _task->cycleBuzzerMode(); _dirty = true; diff --git a/examples/companion_radio/ui-new/ToolsScreen.h b/examples/companion_radio/ui-new/ToolsScreen.h index f738718a..316ccc9b 100644 --- a/examples/companion_radio/ui-new/ToolsScreen.h +++ b/examples/companion_radio/ui-new/ToolsScreen.h @@ -16,21 +16,25 @@ public: display.setTextSize(1); display.setColor(DisplayDriver::LIGHT); display.drawTextCentered(display.width() / 2, 0, "TOOLS"); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, display.headerH() - 1, display.width(), 1); + + int item_h = display.lineStep(); + int start_y = display.listStart(); + int cw = display.getCharWidth(); for (int i = 0; i < ITEM_COUNT; i++) { - int y = 12 + i * 12; + int y = start_y + i * item_h; bool sel = (i == _sel); if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, y - 1, display.width(), 11); + display.fillRect(0, y - 1, display.width(), item_h); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); } display.setCursor(0, y); display.print(sel ? ">" : " "); - display.setCursor(8, y); + display.setCursor(cw + 2, y); display.print(ITEMS[i]); } display.setColor(DisplayDriver::LIGHT); diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index f72b4afd..6d95f059 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -78,10 +78,11 @@ public: // version info at sz2 int ver_y = logo_y + 13 + 2; display.setTextSize(2); + int lh2 = display.getLineHeight(); display.drawTextCentered(display.width()/2, ver_y, _version_info); - // build date at sz1 — sz2 lh is always 16 - int date_y = ver_y + 16 + 2; + // build date at sz1, below sz2 version + int date_y = ver_y + lh2 + 2; display.setTextSize(1); display.drawTextCentered(display.width()/2, date_y, FIRMWARE_BUILD_DATE); @@ -239,6 +240,10 @@ class HomeScreen : public UIScreen { display.setTextSize(1); display.setColor(DisplayDriver::LIGHT); + const int lh = display.getLineHeight(); + const int cw = display.getCharWidth(); + const int ind = cw + 2; // single-char indicator width + int battLeftX; if (mode == 1) { // percent char buf[6]; @@ -252,52 +257,54 @@ class HomeScreen : public UIScreen { battLeftX = display.width() - display.getTextWidth(buf) - 1; display.setCursor(battLeftX, 0); display.print(buf); - } else { // icon - const int iconWidth = 24, iconHeight = 8; - battLeftX = display.width() - iconWidth - 5; - display.drawRect(battLeftX, 0, iconWidth, iconHeight); - display.fillRect(battLeftX + iconWidth, iconHeight / 4, 3, iconHeight / 2); - int fillWidth = (pct * (iconWidth - 4)) / 100; - display.fillRect(battLeftX + 2, 2, fillWidth, iconHeight - 4); + } else { // icon — scales with lh + const int iconH = lh; + const int iconW = iconH * 3; + battLeftX = display.width() - iconW - 3; + display.drawRect(battLeftX, 0, iconW, iconH); + display.fillRect(battLeftX + iconW, iconH / 4, 2, iconH / 2); + int fillW = (pct * (iconW - 4)) / 100; + display.fillRect(battLeftX + 2, 2, fillW, iconH - 4); } #ifdef PIN_BUZZER if (_task->isBuzzerQuiet()) { display.setColor(DisplayDriver::LIGHT); - display.drawXbm(battLeftX - 9, 0, muted_icon, 8, 8); + int mx = battLeftX - ind - 1; + display.fillRect(mx, 0, ind, lh); + display.setColor(DisplayDriver::DARK); + display.setCursor(mx + 1, 0); + display.print("M"); + display.setColor(DisplayDriver::LIGHT); + battLeftX = mx; } #endif // BT connection indicator (left of muted/battery icons) int leftmostX = battLeftX; if (_task->isSerialEnabled()) { -#ifdef PIN_BUZZER - int btX = battLeftX - 18; -#else - int btX = battLeftX - 9; -#endif + int btX = battLeftX - ind - 1; if (_task->hasConnection()) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(btX - 1, 0, 7, 7); + display.fillRect(btX, 0, ind, lh); display.setColor(DisplayDriver::DARK); - display.setCursor(btX, 0); + display.setCursor(btX + 1, 0); display.print("B"); display.setColor(DisplayDriver::LIGHT); } else { - display.setColor(DisplayDriver::LIGHT); - display.setCursor(btX, 0); + display.setCursor(btX + 1, 0); display.print("b"); } leftmostX = btX - 1; - // "A" indicator — left of BT, blinks 50% duty at 1s period + // "A" indicator — left of BT, blinks 50% duty at 4s period if (_node_prefs && _node_prefs->advert_auto_interval_sec > 0) { - int aX = leftmostX - 8; + int aX = leftmostX - ind; if ((millis() % 4000) < 2000) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(aX - 1, 0, 7, 7); + display.fillRect(aX, 0, ind, lh); display.setColor(DisplayDriver::DARK); - display.setCursor(aX, 0); + display.setCursor(aX + 1, 0); display.print("A"); display.setColor(DisplayDriver::LIGHT); } @@ -413,15 +420,17 @@ public: if (show_sec) sprintf(buf, "%02d:%02d:%02d", ti->tm_hour, ti->tm_min, ti->tm_sec); else sprintf(buf, "%02d:%02d", ti->tm_hour, ti->tm_min); } + int lh2 = display.getLineHeight(); // sz2 height: 16 (OLED) or 32 (landscape) display.drawTextCentered(display.width() / 2, 0, buf); 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"}; sprintf(buf, "%s %d %s %d", wd[ti->tm_wday], ti->tm_mday, mo[ti->tm_mon], 1900 + ti->tm_year); - display.drawTextCentered(display.width() / 2, 19, buf); + int date_y = lh2 + 2; + display.drawTextCentered(display.width() / 2, date_y, buf); - int sep_y = 19 + lh + 1; + int sep_y = date_y + lh + 1; int dash0 = sep_y + 3; display.fillRect(0, sep_y, display.width(), 1); @@ -706,7 +715,9 @@ public: display.drawTextCentered(display.width() / 2, content_y + step, "hibernating..."); } else { display.drawXbm((display.width() - 32) / 2, content_y, power_icon, 32, 32); - display.drawTextCentered(display.width() / 2, content_y + 32 + 3, "hibernate:" PRESS_LABEL); + int hint_base = content_y + 32 + 3; + display.drawTextCentered(display.width() / 2, hint_base, "hibernate:"); + display.drawTextCentered(display.width() / 2, hint_base + step, PRESS_LABEL); } } bool auto_adv = _node_prefs && _node_prefs->advert_auto_interval_sec > 0; diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 01a50eb3..17481946 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -92,7 +92,7 @@ void GxEPDDisplay::setTextSize(int sz) { break; case 2: display.setFont(NULL); - display.setTextSize(2); + display.setTextSize((width() >= height()) ? 4 : 2); break; default: display.setFont(_use_lemon ? &Lemon : NULL); diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index c5fa53fc..6cffc323 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -73,13 +73,13 @@ public: int getCharWidth() const override { int sc = (width() >= height()) ? 2 : 1; if (_text_sz == 3) return 17; - if (_text_sz == 2) return 12; + if (_text_sz == 2) return 12 * sc; return (_use_lemon ? 5 : 6) * sc; } int getLineHeight() const override { int sc = (width() >= height()) ? 2 : 1; if (_text_sz == 3) return 28; - if (_text_sz == 2) return 16; + if (_text_sz == 2) return 16 * sc; return (_use_lemon ? 10 : 8) * sc; } void setLemonFont(bool enabled) override { _use_lemon = enabled; }