From 744f2639321a1dc7621914e94ca49a4ad41b682b Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Fri, 22 May 2026 18:05:09 +0200 Subject: [PATCH] refactor: replace all hardcoded pixel positions with layout helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add lineStep(), headerH(), listStart(), listVisible(), valCol() to DisplayDriver so layout derives from getLineHeight() instead of fixed OLED constants. Refactor all screens (SettingsScreen, NearbyScreen, FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen, AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus the lock screen and splash screen in UITask.cpp. On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all text and widgets now scale correctly to the 250×122 display. Co-Authored-By: Claude Sonnet 4.6 --- .../companion_radio/ui-new/AutoAdvertScreen.h | 19 +- examples/companion_radio/ui-new/BotScreen.h | 23 +-- .../ui-new/DashboardConfigScreen.h | 15 +- .../ui-new/FullscreenMsgView.h | 15 +- .../companion_radio/ui-new/NearbyScreen.h | 117 ++++++------ .../companion_radio/ui-new/QuickMsgScreen.h | 171 +++++++++--------- .../ui-new/RingtoneEditorScreen.h | 61 ++++--- .../companion_radio/ui-new/SettingsScreen.h | 71 ++++---- examples/companion_radio/ui-new/UITask.cpp | 143 +++++++++------ src/helpers/ui/DisplayDriver.h | 10 + 10 files changed, 358 insertions(+), 287 deletions(-) diff --git a/examples/companion_radio/ui-new/AutoAdvertScreen.h b/examples/companion_radio/ui-new/AutoAdvertScreen.h index 114d0d02..4899f29d 100644 --- a/examples/companion_radio/ui-new/AutoAdvertScreen.h +++ b/examples/companion_radio/ui-new/AutoAdvertScreen.h @@ -25,22 +25,27 @@ public: int render(DisplayDriver& display) override { display.setTextSize(1); display.setColor(DisplayDriver::LIGHT); - display.drawTextCentered(display.width() / 2, 0, "AUTO-ADVERT"); - display.fillRect(0, 10, display.width(), 1); + int label_y = display.listStart(); + int bar_y = label_y + display.lineStep(); + int bar_h = display.lineStep(); + int tip_y = bar_y + bar_h + display.lineStep(); - display.setCursor(2, 14); + display.drawTextCentered(display.width() / 2, 0, "AUTO-ADVERT"); + display.fillRect(0, display.headerH() - 1, display.width(), 1); + + display.setCursor(2, label_y); display.print("Interval:"); int idx = currentIdx(); display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, 24, display.width(), 12); + display.fillRect(0, bar_y, display.width(), bar_h); display.setColor(DisplayDriver::DARK); - display.drawTextCentered(display.width() / 2, 25, OPT_LABELS[idx]); + display.drawTextCentered(display.width() / 2, bar_y + 1, OPT_LABELS[idx]); display.setColor(DisplayDriver::LIGHT); - display.setCursor(2, 40); + display.setCursor(2, tip_y); display.print("< > to change"); - display.setCursor(2, 51); + display.setCursor(2, tip_y + display.lineStep()); display.print("[Esc] to save"); return 500; } diff --git a/examples/companion_radio/ui-new/BotScreen.h b/examples/companion_radio/ui-new/BotScreen.h index cf0b0ec8..027f7dfb 100644 --- a/examples/companion_radio/ui-new/BotScreen.h +++ b/examples/companion_radio/ui-new/BotScreen.h @@ -8,9 +8,6 @@ class BotScreen : public UIScreen { // Items: 0=Enable(DM), 1=Channel, 2=Trigger, 3=Reply DM, 4=Reply Ch static const int ITEM_COUNT = 5; - static const int ITEM_H = 11; - static const int START_Y = 12; - static const int VAL_X = 70; int _sel; bool _dirty; @@ -58,20 +55,24 @@ public: return _kb.render(display); } + int item_h = display.lineStep(); + int start_y = display.listStart(); + int val_x = display.valCol(); + display.drawTextCentered(display.width() / 2, 0, "AUTO-REPLY BOT"); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, display.headerH() - 1, display.width(), 1); static const char* labels[] = { "Enable", "Channel", "Trigger", "Reply DM", "Reply Ch" }; for (int i = 0; i < ITEM_COUNT; i++) { - int y = START_Y + i * ITEM_H; + int y = start_y + i * item_h; bool sel = (i == _sel); if (sel) { - display.fillRect(0, y - 1, display.width(), ITEM_H); + display.fillRect(0, y - 1, display.width(), item_h); display.setColor(DisplayDriver::DARK); } display.setCursor(2, y); display.print(labels[i]); - display.setCursor(VAL_X, y); + display.setCursor(val_x, y); if (i == 0) { display.print(_prefs->bot_enabled ? "ON" : "OFF"); @@ -81,19 +82,19 @@ public: } else { ChannelDetails ch; if (the_mesh.getChannel(_prefs->bot_channel_idx, ch) && ch.name[0]) - display.drawTextEllipsized(VAL_X, y, display.width() - VAL_X - 1, ch.name); + display.drawTextEllipsized(val_x, y, display.width() - val_x - 1, ch.name); else display.print("?"); } } else if (i == 2) { const char* tr = _prefs->bot_trigger; - display.drawTextEllipsized(VAL_X, y, display.width() - VAL_X - 1, tr[0] ? tr : "(none)"); + display.drawTextEllipsized(val_x, y, display.width() - val_x - 1, tr[0] ? tr : "(none)"); } else if (i == 3) { const char* rp = _prefs->bot_reply_dm; - display.drawTextEllipsized(VAL_X, y, display.width() - VAL_X - 1, rp[0] ? rp : "(none)"); + display.drawTextEllipsized(val_x, y, display.width() - val_x - 1, rp[0] ? rp : "(none)"); } else { const char* rp = _prefs->bot_reply_ch; - display.drawTextEllipsized(VAL_X, y, display.width() - VAL_X - 1, rp[0] ? rp : "(none)"); + display.drawTextEllipsized(val_x, y, display.width() - val_x - 1, rp[0] ? rp : "(none)"); } display.setColor(DisplayDriver::LIGHT); } diff --git a/examples/companion_radio/ui-new/DashboardConfigScreen.h b/examples/companion_radio/ui-new/DashboardConfigScreen.h index f587c6d5..b60011b3 100644 --- a/examples/companion_radio/ui-new/DashboardConfigScreen.h +++ b/examples/companion_radio/ui-new/DashboardConfigScreen.h @@ -21,9 +21,6 @@ class DashboardConfigScreen : public UIScreen { NodePrefs* _prefs; static const int FIELD_SLOTS = 3; - static const int ITEM_H = 13; - static const int START_Y = 13; - static const int VAL_X = 62; static const char* OPTION_NAMES[DASH_COUNT]; @@ -44,23 +41,27 @@ public: int render(DisplayDriver& display) override { display.setTextSize(1); display.setColor(DisplayDriver::LIGHT); + int item_h = display.lineStep(); + int start_y = display.listStart(); + int val_x = display.valCol(); + display.drawTextCentered(display.width() / 2, 0, "CLOCK FIELDS"); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, display.headerH() - 1, display.width(), 1); static const char* labels[] = { "Field 1", "Field 2", "Field 3" }; for (int i = 0; i < FIELD_SLOTS; i++) { - int y = START_Y + i * ITEM_H; + int y = start_y + i * item_h; bool sel = (i == _sel); if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, y - 1, display.width(), ITEM_H); + display.fillRect(0, y - 1, display.width(), item_h); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); } display.setCursor(2, y); display.print(labels[i]); - display.setCursor(VAL_X, y); + display.setCursor(val_x, y); uint8_t f = _prefs->dashboard_fields[i]; display.print(OPTION_NAMES[f < DASH_COUNT ? f : DASH_NONE]); display.setColor(DisplayDriver::LIGHT); diff --git a/examples/companion_radio/ui-new/FullscreenMsgView.h b/examples/companion_radio/ui-new/FullscreenMsgView.h index bff73713..f83b6359 100644 --- a/examples/companion_radio/ui-new/FullscreenMsgView.h +++ b/examples/companion_radio/ui-new/FullscreenMsgView.h @@ -82,7 +82,8 @@ struct FullscreenMsgView { } } - const int header_h = to_nick[0] ? 20 : 10; + const int cw = display.getCharWidth(); + const int header_h = to_nick[0] ? (lineH * 2 + 4) : (lineH + 2); const int startY = header_h + 2; const int visible = (display.height() - startY - lineH) / lineH; @@ -94,7 +95,7 @@ struct FullscreenMsgView { char trans_nick[32], to_label[36]; display.translateUTF8ToBlocks(trans_nick, to_nick, sizeof(trans_nick)); snprintf(to_label, sizeof(to_label), "To: %s", trans_nick); - display.drawTextEllipsized(2, 11, display.width() - 4, to_label); + display.drawTextEllipsized(2, lineH + 3, display.width() - 4, to_label); } display.setColor(DisplayDriver::LIGHT); @@ -111,16 +112,16 @@ struct FullscreenMsgView { } if (scroll > 0) { display.setColor(DisplayDriver::DARK); - display.fillRect(display.width() - 6, startY, 6, lineH); + display.fillRect(display.width() - cw, startY, cw, lineH); display.setColor(DisplayDriver::LIGHT); - display.setCursor(display.width() - 6, startY); + display.setCursor(display.width() - cw, startY); display.print("^"); } if (scroll < max_scroll) { display.setColor(DisplayDriver::DARK); - display.fillRect(display.width() - 6, startY + (visible - 1) * lineH, 6, lineH); + display.fillRect(display.width() - cw, startY + (visible - 1) * lineH, cw, lineH); display.setColor(DisplayDriver::LIGHT); - display.setCursor(display.width() - 6, startY + (visible - 1) * lineH); + display.setCursor(display.width() - cw, startY + (visible - 1) * lineH); display.print("v"); } const int nav_y = display.height() - lineH; @@ -129,7 +130,7 @@ struct FullscreenMsgView { display.print("<"); } if (has_prev) { - display.setCursor(display.width() - 6, nav_y); + display.setCursor(display.width() - cw, nav_y); display.print(">"); } return 2000; diff --git a/examples/companion_radio/ui-new/NearbyScreen.h b/examples/companion_radio/ui-new/NearbyScreen.h index 1f656d27..315f79cb 100644 --- a/examples/companion_radio/ui-new/NearbyScreen.h +++ b/examples/companion_radio/ui-new/NearbyScreen.h @@ -8,10 +8,7 @@ class NearbyScreen : public UIScreen { UITask* _task; - static const int VISIBLE = 4; - static const int ITEM_H = 12; - static const int START_Y = 12; - static const int DIST_COL = 86; + int _visible = 4; // updated each render; used by handleInput for scroll clamping static const int FILTER_COUNT = 6; static const char* FILTER_LABELS[FILTER_COUNT]; @@ -54,10 +51,7 @@ class NearbyScreen : public UIScreen { int _dsel; bool _ddetail; - static const int D_BOX_H = 19; - static const int D_ITEM_H = 21; - static const int D_VISIBLE = 2; - static const int D_START_Y = 11; + int _d_visible = 2; // updated each render; used by handleInputDiscover // ── helpers ────────────────────────────────────────────────────────────────── static void pubKeyToBase64(const uint8_t* key, char* out, int out_len) { @@ -193,6 +187,14 @@ class NearbyScreen : public UIScreen { } int renderDiscover(DisplayDriver& display) { + int lh = display.getLineHeight(); + int hdr = display.headerH(); + int d_box_h = 2 * lh + 3; // two text rows + padding + int d_item_h = d_box_h + 2; + int d_start_y = hdr; + _d_visible = display.listVisible(d_item_h); + if (_d_visible < 1) _d_visible = 1; + if (_ddetail) { // ── full-screen detail for selected node ────────────────────────────── const DiscoverResult& r = _dresults[_dsel]; @@ -207,29 +209,29 @@ class NearbyScreen : public UIScreen { char filtered[32]; display.translateUTF8ToBlocks(filtered, label, sizeof(filtered)); display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, 0, display.width(), 10); + display.fillRect(0, 0, display.width(), hdr - 1); display.setColor(DisplayDriver::DARK); display.drawTextEllipsized(2, 1, display.width() - 4, filtered); display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, hdr - 1, display.width(), 1); - // public key as base64, truncated with ... by drawTextEllipsized + // public key as base64 char b64[48]; pubKeyToBase64(r.pub_key, b64, sizeof(b64)); - display.drawTextEllipsized(2, 12, display.width() - 4, b64); + display.drawTextEllipsized(2, hdr, display.width() - 4, b64); + int step = lh + 1; char buf[32]; - snprintf(buf, sizeof(buf), "RSSI: %d dBm", (int)r.rssi); - display.setCursor(2, 21); display.print(buf); + display.setCursor(2, hdr + step); display.print(buf); snprintf(buf, sizeof(buf), "SNR: %d dB", (int)(r.snr_x4 / 4)); - display.setCursor(2, 30); display.print(buf); + display.setCursor(2, hdr + step * 2); display.print(buf); snprintf(buf, sizeof(buf), "Rem: %d dB", (int)(r.remote_snr_x4 / 4)); - display.setCursor(2, 39); display.print(buf); + display.setCursor(2, hdr + step * 3); display.print(buf); - display.setCursor(2, 48); + display.setCursor(2, hdr + step * 4); display.print(r.is_known ? "Status: known" : "Status: new"); return 5000; @@ -250,21 +252,22 @@ class NearbyScreen : public UIScreen { else snprintf(title, sizeof(title), "DISCOVER (%d found)", _dresult_count); display.drawTextCentered(display.width() / 2, 0, title); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, hdr - 1, display.width(), 1); if (_dresult_count == 0) { - display.drawTextCentered(display.width() / 2, 32, + display.drawTextCentered(display.width() / 2, display.height() / 2, _discovering ? "Waiting for replies..." : "No nodes found"); } else { if (_dsel >= _dresult_count) _dsel = _dresult_count - 1; - if (_dscroll > _dresult_count - D_VISIBLE) - _dscroll = _dresult_count > D_VISIBLE ? _dresult_count - D_VISIBLE : 0; + if (_dscroll > _dresult_count - _d_visible) + _dscroll = _dresult_count > _d_visible ? _dresult_count - _d_visible : 0; if (_dscroll < 0) _dscroll = 0; - for (int i = 0; i < D_VISIBLE && (_dscroll + i) < _dresult_count; i++) { + + for (int i = 0; i < _d_visible && (_dscroll + i) < _dresult_count; i++) { int idx = _dscroll + i; bool sel = (idx == _dsel); const DiscoverResult& r = _dresults[idx]; - int y = D_START_Y + i * D_ITEM_H; + int y = d_start_y + i * d_item_h; const char* typeStr = (r.type == ADV_TYPE_REPEATER) ? "Rpt" : (r.type == ADV_TYPE_SENSOR) ? "Snsr" : @@ -272,11 +275,11 @@ class NearbyScreen : public UIScreen { display.setColor(DisplayDriver::LIGHT); if (sel) { - display.fillRect(0, y, display.width(), D_BOX_H); // fully inverted when selected + display.fillRect(0, y, display.width(), d_box_h); display.setColor(DisplayDriver::DARK); } else { - display.drawRect(0, y, display.width(), D_BOX_H); - display.fillRect(1, y + 1, display.width() - 2, 8); + display.drawRect(0, y, display.width(), d_box_h); + display.fillRect(1, y + 1, display.width() - 2, lh); display.setColor(DisplayDriver::DARK); } @@ -300,14 +303,15 @@ class NearbyScreen : public UIScreen { display.setColor(sel ? DisplayDriver::DARK : DisplayDriver::LIGHT); char sig[24]; snprintf(sig, sizeof(sig), "RSSI:%d SNR:%d", (int)r.rssi, (int)(r.snr_x4 / 4)); - display.drawTextEllipsized(3, y + 10, display.width() - 6, sig); + display.drawTextEllipsized(3, y + lh + 2, display.width() - 6, sig); } display.setColor(DisplayDriver::LIGHT); + int cw = display.getCharWidth(); if (_dscroll > 0) - { display.setCursor(display.width() - 6, D_START_Y + 1); display.print("^"); } - if (_dscroll + D_VISIBLE < _dresult_count) - { display.setCursor(display.width() - 6, D_START_Y + D_VISIBLE * D_ITEM_H - 10); display.print("v"); } + { display.setCursor(display.width() - cw, d_start_y); display.print("^"); } + if (_dscroll + _d_visible < _dresult_count) + { display.setCursor(display.width() - cw, d_start_y + (_d_visible - 1) * d_item_h); display.print("v"); } } return _discovering ? 200 : 2000; @@ -342,7 +346,7 @@ class NearbyScreen : public UIScreen { } if (c == KEY_DOWN && _dsel < _dresult_count - 1) { _dsel++; - if (_dsel >= _dscroll + D_VISIBLE) _dscroll = _dsel - D_VISIBLE + 1; + if (_dsel >= _dscroll + _d_visible) _dscroll = _dsel - _d_visible + 1; return true; } return true; @@ -390,9 +394,12 @@ public: // ── detail view ────────────────────────────────────────────────────────── if (_detail && _sel < _count) { const Entry& e = _entries[_sel]; + int lh = display.getLineHeight(); + int hdr = display.headerH(); + int step = lh + 1; display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, 0, display.width(), 10); + display.fillRect(0, 0, display.width(), hdr - 1); display.setColor(DisplayDriver::DARK); char filtered[32]; display.translateUTF8ToBlocks(filtered, e.name, sizeof(filtered)); @@ -401,55 +408,60 @@ public: char buf[32]; snprintf(buf, sizeof(buf), "Lat: %.5f", e.lat_e6 / 1e6); - display.setCursor(2, 11); display.print(buf); + display.setCursor(2, hdr); display.print(buf); snprintf(buf, sizeof(buf), "Lon: %.5f", e.lon_e6 / 1e6); - display.setCursor(2, 20); display.print(buf); + display.setCursor(2, hdr + step); display.print(buf); if (e.dist_km >= 0.0f) { char dist[12]; fmtDist(dist, sizeof(dist), e.dist_km); int az = bearingDeg(_own_lat, _own_lon, e.lat_e6, e.lon_e6); snprintf(buf, sizeof(buf), "Dist: %s", dist); - display.setCursor(2, 29); display.print(buf); + display.setCursor(2, hdr + step * 2); display.print(buf); snprintf(buf, sizeof(buf), "Az: %dd (%s)", az, bearingCardinal(az)); - display.setCursor(2, 38); display.print(buf); + display.setCursor(2, hdr + step * 3); display.print(buf); } else { - display.setCursor(2, 29); display.print("Dist: no own GPS"); - display.setCursor(2, 38); display.print("Az: unknown"); + display.setCursor(2, hdr + step * 2); display.print("Dist: no own GPS"); + display.setCursor(2, hdr + step * 3); display.print("Az: unknown"); } snprintf(buf, sizeof(buf), "Type: %s", typeName(e.type)); - display.setCursor(2, 47); display.print(buf); + display.setCursor(2, hdr + step * 4); display.print(buf); char age[16]; fmtAge(age, sizeof(age), e.lastmod); snprintf(buf, sizeof(buf), "Seen: %s", age); - display.setCursor(2, 56); display.print(buf); + display.setCursor(2, hdr + step * 5); display.print(buf); return 2000; } // ── list view ──────────────────────────────────────────────────────────── + int item_h = display.lineStep(); + int start_y = display.listStart(); + int dist_col = display.width() - display.getCharWidth() * 7; + _visible = display.listVisible(item_h); + display.setColor(DisplayDriver::LIGHT); char title[22]; snprintf(title, sizeof(title), "NEARBY[%s]", FILTER_LABELS[_filter]); display.drawTextCentered(display.width() / 2, 0, title); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, display.headerH() - 1, display.width(), 1); if (_count == 0) { - display.drawTextCentered(display.width() / 2, 28, "No contacts found"); - display.drawTextCentered(display.width() / 2, 40, "[Enter]=Discover"); + display.drawTextCentered(display.width() / 2, display.height() / 2 - display.lineStep() / 2, "No contacts found"); + display.drawTextCentered(display.width() / 2, display.height() / 2 + display.lineStep() / 2, "[Enter]=Discover"); } else { - for (int i = 0; i < VISIBLE && (_scroll + i) < _count; i++) { + for (int i = 0; i < _visible && (_scroll + i) < _count; i++) { int idx = _scroll + i; bool sel = (idx == _sel); - int y = START_Y + i * ITEM_H; + int y = start_y + i * item_h; const Entry& e = _entries[idx]; if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, y - 1, display.width(), ITEM_H - 1); + display.fillRect(0, y - 1, display.width(), item_h - 1); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); @@ -457,21 +469,22 @@ public: char filt[32]; display.translateUTF8ToBlocks(filt, e.name, sizeof(filt)); - display.drawTextEllipsized(2, y, DIST_COL - 4, filt); + display.drawTextEllipsized(2, y, dist_col - 4, filt); display.setColor(sel ? DisplayDriver::DARK : DisplayDriver::LIGHT); char dist[10]; if (e.dist_km >= 0.0f) fmtDist(dist, sizeof(dist), e.dist_km); else strncpy(dist, "?GPS", sizeof(dist)); - display.setCursor(DIST_COL, y); + display.setCursor(dist_col, y); display.print(dist); } display.setColor(DisplayDriver::LIGHT); + int cw = display.getCharWidth(); if (_scroll > 0) - { display.setCursor(display.width() - 6, START_Y); display.print("^"); } - if (_scroll + VISIBLE < _count) - { display.setCursor(display.width() - 6, START_Y + (VISIBLE - 1) * ITEM_H); display.print("v"); } + { display.setCursor(display.width() - cw, start_y); display.print("^"); } + if (_scroll + _visible < _count) + { display.setCursor(display.width() - cw, start_y + (_visible - 1) * item_h); display.print("v"); } } if (_ctx_menu.active) { @@ -519,7 +532,7 @@ public: } if (c == KEY_DOWN && _sel < _count - 1) { _sel++; - if (_sel >= _scroll + VISIBLE) _scroll = _sel - VISIBLE + 1; + if (_sel >= _scroll + _visible) _scroll = _sel - _visible + 1; return true; } if (c == KEY_ENTER && _count == 0) { enterDiscoverMode(); return true; } diff --git a/examples/companion_radio/ui-new/QuickMsgScreen.h b/examples/companion_radio/ui-new/QuickMsgScreen.h index 6febeba3..86c25d5d 100644 --- a/examples/companion_radio/ui-new/QuickMsgScreen.h +++ b/examples/companion_radio/ui-new/QuickMsgScreen.h @@ -61,13 +61,8 @@ class QuickMsgScreen : public UIScreen { int _dm_hist_sel, _dm_hist_scroll; FullscreenMsgView _dm_fs; - static const int VISIBLE = 4; - static const int ITEM_H = 12; - static const int START_Y = 12; - static const int HIST_VISIBLE = 2; // 2-line boxed history entries - static const int HIST_ITEM_H = 21; // box(19) + gap(2) - static const int HIST_BOX_H = 19; - static const int HIST_START_Y = 11; + int _visible = 4; // updated in render(); used by handleInput() for scroll clamping + int _hist_visible = 2; // updated in render(); for history list scroll clamping void expandMsg(const char* tmpl, char* out, int out_len) const { double lat = 0, lon = 0; @@ -128,17 +123,6 @@ class QuickMsgScreen : public UIScreen { } } - void renderScrollHints(DisplayDriver& display, int scroll, int count) { - display.setColor(DisplayDriver::LIGHT); - if (scroll > 0) { - display.setCursor(display.width() - 6, START_Y); - display.print("^"); - } - if (scroll + VISIBLE < count) { - display.setCursor(display.width() - 6, START_Y + (VISIBLE-1)*ITEM_H); - display.print("v"); - } - } static void fmtMsgAge(char* buf, int n, uint32_t timestamp) { uint32_t now = rtc_clock.getCurrentTime(); @@ -476,9 +460,15 @@ public: display.setTextSize(1); display.setColor(DisplayDriver::LIGHT); + int lh = display.getLineHeight(); + int item_h = display.lineStep(); + int start_y = display.listStart(); + int cw = display.getCharWidth(); + _visible = display.listVisible(item_h); + if (_phase == MODE_SELECT) { display.drawTextCentered(display.width()/2, 0, "MESSAGE"); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, display.headerH() - 1, display.width(), 1); const char* opts[] = { "Direct message", "Channels", "Room Servers" }; int badges[3] = { getDMUnreadTotal(), @@ -486,18 +476,18 @@ public: _task->getRoomUnreadCount() }; for (int i = 0; i < 3; i++) { - int y = START_Y + i * ITEM_H; + int y = start_y + i * item_h; bool sel = (i == _mode_sel); if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, y - 1, display.width(), ITEM_H - 1); + display.fillRect(0, y - 1, display.width(), item_h - 1); 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(opts[i]); if (badges[i] > 0) { char badge[5]; @@ -511,22 +501,22 @@ public: } else if (_phase == CONTACT_PICK) { display.drawTextCentered(display.width()/2, 0, _room_mode ? "SELECT ROOM" : "SELECT CONTACT"); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, display.headerH() - 1, display.width(), 1); if (_num_contacts == 0) { - display.drawTextCentered(display.width()/2, 32, _room_mode ? "No room servers" : "No favourites"); + display.drawTextCentered(display.width()/2, display.height()/2, _room_mode ? "No room servers" : "No favourites"); return 5000; } - for (int i = 0; i < VISIBLE && (_contact_scroll+i) < _num_contacts; i++) { + for (int i = 0; i < _visible && (_contact_scroll+i) < _num_contacts; i++) { int list_idx = _contact_scroll + i; int mesh_idx = _sorted[list_idx]; bool sel = (list_idx == _contact_sel); - int y = START_Y + i * ITEM_H; + int y = start_y + i * item_h; if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, y - 1, display.width(), ITEM_H - 1); + display.fillRect(0, y - 1, display.width(), item_h - 1); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); @@ -545,7 +535,7 @@ public: snprintf(badge, sizeof(badge), "%d", (int)dm_unread); bw = display.getTextWidth(badge) + 2; } - display.drawTextEllipsized(8, y, display.width() - 8 - bw - 1, filtered); + display.drawTextEllipsized(cw + 2, y, display.width() - cw - 2 - bw - 1, filtered); if (dm_unread > 0) { display.setCursor(display.width() - bw + 1, y); display.print(badge); @@ -553,28 +543,29 @@ public: } } display.setColor(DisplayDriver::LIGHT); - renderScrollHints(display, _contact_scroll, _num_contacts); + if (_contact_scroll > 0) { display.setCursor(display.width() - cw, start_y); display.print("^"); } + if (_contact_scroll + _visible < _num_contacts) { display.setCursor(display.width() - cw, start_y + (_visible-1)*item_h); display.print("v"); } // Context menu overlay if (_ctx_menu.active) _ctx_menu.render(display); } else if (_phase == CHANNEL_PICK) { display.drawTextCentered(display.width()/2, 0, "SELECT CHANNEL"); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, display.headerH() - 1, display.width(), 1); if (_num_channels == 0) { - display.drawTextCentered(display.width()/2, 32, "No channels"); + display.drawTextCentered(display.width()/2, display.height()/2, "No channels"); return 5000; } - for (int i = 0; i < VISIBLE && (_channel_scroll+i) < _num_channels; i++) { + for (int i = 0; i < _visible && (_channel_scroll+i) < _num_channels; i++) { int list_idx = _channel_scroll + i; bool sel = (list_idx == _channel_sel); - int y = START_Y + i * ITEM_H; + int y = start_y + i * item_h; if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, y - 1, display.width(), ITEM_H - 1); + display.fillRect(0, y - 1, display.width(), item_h - 1); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); @@ -590,7 +581,7 @@ public: snprintf(badge, sizeof(badge), "%d", (int)unread); bw = display.getTextWidth(badge) + 2; } - display.drawTextEllipsized(8, y, display.width() - 10 - bw, ch.name); + display.drawTextEllipsized(cw + 2, y, display.width() - cw - 4 - bw, ch.name); if (unread > 0) { display.setCursor(display.width() - bw, y); display.print(badge); @@ -598,7 +589,8 @@ public: } } display.setColor(DisplayDriver::LIGHT); - renderScrollHints(display, _channel_scroll, _num_channels); + if (_channel_scroll > 0) { display.setCursor(display.width() - cw, start_y); display.print("^"); } + if (_channel_scroll + _visible < _num_channels) { display.setCursor(display.width() - cw, start_y + (_visible-1)*item_h); display.print("v"); } // Context menu overlay if (_ctx_menu.active) _ctx_menu.render(display); @@ -623,18 +615,25 @@ public: return 500; } + int hist_box_h = 2 * lh + 3; + int hist_item_h = hist_box_h + 2; + int hist_start_y = display.headerH(); + _hist_visible = (display.height() - hist_start_y) / hist_item_h; + if (_hist_visible < 1) _hist_visible = 1; + int cby = hist_start_y + _hist_visible * hist_item_h + 2; + char title[24]; display.setColor(DisplayDriver::LIGHT); snprintf(title, sizeof(title), "%.23s", filtered_name); display.drawTextCentered(display.width()/2, 0, title); - display.fillRect(0, 9, display.width(), 1); + display.fillRect(0, lh + 1, display.width(), 1); int dm_count = dmHistCountForContact(_sel_contact.id.pub_key); - for (int i = 0; i < HIST_VISIBLE && (_dm_hist_scroll + i) < dm_count; i++) { + for (int i = 0; i < _hist_visible && (_dm_hist_scroll + i) < dm_count; i++) { int item = _dm_hist_scroll + i; bool sel = (item == _dm_hist_sel); - int y = HIST_START_Y + i * HIST_ITEM_H; + int y = hist_start_y + i * hist_item_h; int ring_pos = dmHistEntryForContact(_sel_contact.id.pub_key, item); if (ring_pos < 0) continue; @@ -647,44 +646,44 @@ public: if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, y, display.width(), HIST_BOX_H); + display.fillRect(0, y, display.width(), hist_box_h); display.setColor(DisplayDriver::DARK); - display.drawTextEllipsized(3, y + 1, display.width() - 6 - age_w, sender); + display.drawTextEllipsized(3, y + 1, display.width() - cw - 2 - age_w, sender); if (age[0]) { display.setCursor(display.width() - 3 - display.getTextWidth(age), y + 1); display.print(age); } - display.drawTextEllipsized(3, y + 10, display.width() - 6, skipReplyPrefix(e.text)); + display.drawTextEllipsized(3, y + lh + 2, display.width() - cw - 2, skipReplyPrefix(e.text)); } else { display.setColor(DisplayDriver::LIGHT); - display.drawRect(0, y, display.width(), HIST_BOX_H); - display.fillRect(1, y + 1, display.width() - 2, 8); + display.drawRect(0, y, display.width(), hist_box_h); + display.fillRect(1, y + 1, display.width() - 2, lh); display.setColor(DisplayDriver::DARK); - display.drawTextEllipsized(3, y + 1, display.width() - 6 - age_w, sender); + display.drawTextEllipsized(3, y + 1, display.width() - cw - 2 - age_w, sender); if (age[0]) { display.setCursor(display.width() - 3 - display.getTextWidth(age), y + 1); display.print(age); } display.setColor(DisplayDriver::LIGHT); - display.drawTextEllipsized(3, y + 10, display.width() - 6, skipReplyPrefix(e.text)); + display.drawTextEllipsized(3, y + lh + 2, display.width() - cw - 2, skipReplyPrefix(e.text)); } } if (dm_count == 0) { display.setColor(DisplayDriver::LIGHT); - display.drawTextCentered(display.width()/2, 32, "No messages yet"); + display.drawTextCentered(display.width()/2, display.height()/2, "No messages yet"); } display.setColor(DisplayDriver::LIGHT); if (_dm_hist_scroll > 0) { - display.setCursor(display.width() - 6, HIST_START_Y + 1); + display.setCursor(display.width() - cw, hist_start_y + 1); display.print("^"); } - if (_dm_hist_scroll + HIST_VISIBLE < dm_count) { - display.setCursor(display.width() - 6, HIST_START_Y + HIST_VISIBLE * HIST_ITEM_H - 10); + if (_dm_hist_scroll + _hist_visible < dm_count) { + display.setCursor(display.width() - cw, hist_start_y + (_hist_visible - 1) * hist_item_h + hist_box_h - lh); display.print("v"); } bool compose_sel = (_dm_hist_sel == -1); const char* ctxt = "[+ send]"; int ctw = display.getTextWidth(ctxt); - int cbx = 1, cby = 55; + int cbx = 1; if (compose_sel) { - display.fillRect(cbx, cby - 1, ctw + 4, 9); + display.fillRect(cbx, cby - 1, ctw + 4, lh + 1); display.setColor(DisplayDriver::DARK); } display.setCursor(cbx + 2, cby); @@ -718,19 +717,26 @@ public: return 2000; } + int hist_box_h = 2 * lh + 3; + int hist_item_h = hist_box_h + 2; + int hist_start_y = display.headerH(); + _hist_visible = (display.height() - hist_start_y) / hist_item_h; + if (_hist_visible < 1) _hist_visible = 1; + int cby = hist_start_y + _hist_visible * hist_item_h + 2; + ChannelDetails ch; the_mesh.getChannel(_sel_channel_idx, ch); char title[24]; snprintf(title, sizeof(title), "%.23s", ch.name); display.drawTextCentered(display.width()/2, 0, title); - display.fillRect(0, 9, display.width(), 1); + display.fillRect(0, lh + 1, display.width(), 1); int ch_hist_count = histCountForChannel(_sel_channel_idx); - for (int i = 0; i < HIST_VISIBLE && (_hist_scroll + i) < ch_hist_count; i++) { + for (int i = 0; i < _hist_visible && (_hist_scroll + i) < ch_hist_count; i++) { int item = _hist_scroll + i; bool sel = (item == _hist_sel); - int y = HIST_START_Y + i * HIST_ITEM_H; + int y = hist_start_y + i * hist_item_h; int ring_pos = histEntryForChannel(_sel_channel_idx, item); if (ring_pos < 0) continue; @@ -754,36 +760,36 @@ public: if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, y, display.width(), HIST_BOX_H); + display.fillRect(0, y, display.width(), hist_box_h); display.setColor(DisplayDriver::DARK); - display.drawTextEllipsized(3, y + 1, display.width() - 6 - age_w, sender); + display.drawTextEllipsized(3, y + 1, display.width() - cw - 2 - age_w, sender); if (age[0]) { display.setCursor(display.width() - 3 - display.getTextWidth(age), y + 1); display.print(age); } - display.drawTextEllipsized(3, y + 10, display.width() - 6, skipReplyPrefix(msg_part)); + display.drawTextEllipsized(3, y + lh + 2, display.width() - cw - 2, skipReplyPrefix(msg_part)); } else { display.setColor(DisplayDriver::LIGHT); - display.drawRect(0, y, display.width(), HIST_BOX_H); - display.fillRect(1, y + 1, display.width() - 2, 8); + display.drawRect(0, y, display.width(), hist_box_h); + display.fillRect(1, y + 1, display.width() - 2, lh); display.setColor(DisplayDriver::DARK); - display.drawTextEllipsized(3, y + 1, display.width() - 6 - age_w, sender); + display.drawTextEllipsized(3, y + 1, display.width() - cw - 2 - age_w, sender); if (age[0]) { display.setCursor(display.width() - 3 - display.getTextWidth(age), y + 1); display.print(age); } display.setColor(DisplayDriver::LIGHT); - display.drawTextEllipsized(3, y + 10, display.width() - 6, skipReplyPrefix(msg_part)); + display.drawTextEllipsized(3, y + lh + 2, display.width() - cw - 2, skipReplyPrefix(msg_part)); } } if (ch_hist_count == 0) { display.setColor(DisplayDriver::LIGHT); - display.drawTextCentered(display.width()/2, 32, "No messages yet"); + display.drawTextCentered(display.width()/2, display.height()/2, "No messages yet"); } // scroll hints display.setColor(DisplayDriver::LIGHT); if (_hist_scroll > 0) { - display.setCursor(display.width() - 6, HIST_START_Y + 1); + display.setCursor(display.width() - cw, hist_start_y + 1); display.print("^"); } - if (_hist_scroll + HIST_VISIBLE < ch_hist_count) { - display.setCursor(display.width() - 6, HIST_START_Y + HIST_VISIBLE * HIST_ITEM_H - 10); + if (_hist_scroll + _hist_visible < ch_hist_count) { + display.setCursor(display.width() - cw, hist_start_y + (_hist_visible - 1) * hist_item_h + hist_box_h - lh); display.print("v"); } @@ -791,14 +797,14 @@ public: bool compose_sel = (_hist_sel == -1); const char* ctxt = "[+ send]"; int ctw = display.getTextWidth(ctxt); - int cbx = 1, cby = 55; + int cbx = 1; if (compose_sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(cbx - 1, cby - 1, ctw + 4, 10); + display.fillRect(cbx - 1, cby - 1, ctw + 4, lh + 2); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); - display.drawRect(cbx - 1, cby - 1, ctw + 4, 10); + display.drawRect(cbx - 1, cby - 1, ctw + 4, lh + 2); } display.setCursor(cbx + 1, cby); display.print(ctxt); @@ -826,17 +832,17 @@ public: snprintf(title, sizeof(title), "TO:%.14s", _sel_contact.name); } display.drawTextCentered(display.width()/2, 0, title); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, display.headerH() - 1, display.width(), 1); int total_msg_items = 1 + _active_msg_count; - for (int i = 0; i < VISIBLE && (_msg_scroll+i) < total_msg_items; i++) { + for (int i = 0; i < _visible && (_msg_scroll+i) < total_msg_items; i++) { int idx = _msg_scroll + i; bool sel = (idx == _msg_sel); - int y = START_Y + i * ITEM_H; + int y = start_y + i * item_h; if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, y - 1, display.width(), ITEM_H - 1); + display.fillRect(0, y - 1, display.width(), item_h - 1); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); @@ -845,17 +851,18 @@ public: display.print(sel ? ">" : " "); if (idx == 0) { - display.setCursor(8, y); + display.setCursor(cw + 2, y); display.print("Custom message..."); } else { NodePrefs* p = _task->getNodePrefs(); int slot = _active_msgs[idx - 1]; const char* tmpl = p ? p->custom_msgs[slot] : ""; - display.drawTextEllipsized(8, y, display.width() - 10, tmpl); + display.drawTextEllipsized(cw + 2, y, display.width() - cw - 4, tmpl); } } display.setColor(DisplayDriver::LIGHT); - renderScrollHints(display, _msg_scroll, total_msg_items); + if (_msg_scroll > 0) { display.setCursor(display.width() - cw, start_y); display.print("^"); } + if (_msg_scroll + _visible < total_msg_items) { display.setCursor(display.width() - cw, start_y + (_visible-1)*item_h); display.print("v"); } } return 2000; } @@ -911,7 +918,7 @@ public: } if (c == KEY_DOWN && _contact_sel < _num_contacts - 1) { _contact_sel++; - if (_contact_sel >= _contact_scroll + VISIBLE) _contact_scroll = _contact_sel - VISIBLE + 1; + if (_contact_sel >= _contact_scroll + _visible) _contact_scroll = _contact_sel - _visible + 1; return true; } if (c == KEY_ENTER && _num_contacts > 0) { @@ -972,7 +979,7 @@ public: } if (c == KEY_DOWN && _channel_sel < _num_channels - 1) { _channel_sel++; - if (_channel_sel >= _channel_scroll + VISIBLE) _channel_scroll = _channel_sel - VISIBLE + 1; + if (_channel_sel >= _channel_scroll + _visible) _channel_scroll = _channel_sel - _visible + 1; return true; } if (c == KEY_ENTER && _num_channels > 0) { @@ -1055,8 +1062,8 @@ public: if (_dm_hist_sel == -1 && dm_count > 0) { _dm_hist_sel = 0; _dm_hist_scroll = 0; } else if (_dm_hist_sel >= 0 && _dm_hist_sel < dm_count - 1) { _dm_hist_sel++; - if (_dm_hist_sel >= _dm_hist_scroll + HIST_VISIBLE) - _dm_hist_scroll = _dm_hist_sel - HIST_VISIBLE + 1; + if (_dm_hist_sel >= _dm_hist_scroll + _hist_visible) + _dm_hist_scroll = _dm_hist_sel - _hist_visible + 1; } return true; } @@ -1129,7 +1136,7 @@ public: if (_hist_sel == -1 && ch_hist_count > 0) { _hist_sel = 0; _hist_scroll = 0; } else if (_hist_sel >= 0 && _hist_sel < ch_hist_count - 1) { _hist_sel++; - if (_hist_sel >= _hist_scroll + HIST_VISIBLE) _hist_scroll = _hist_sel - HIST_VISIBLE + 1; + if (_hist_sel >= _hist_scroll + _hist_visible) _hist_scroll = _hist_sel - _hist_visible + 1; } updateChannelUnread(); return true; @@ -1183,7 +1190,7 @@ public: } if (c == KEY_DOWN && _msg_sel < total_msg_items - 1) { _msg_sel++; - if (_msg_sel >= _msg_scroll + VISIBLE) _msg_scroll = _msg_sel - VISIBLE + 1; + if (_msg_sel >= _msg_scroll + _visible) _msg_scroll = _msg_sel - _visible + 1; return true; } if (c == KEY_ENTER) { diff --git a/examples/companion_radio/ui-new/RingtoneEditorScreen.h b/examples/companion_radio/ui-new/RingtoneEditorScreen.h index 84cb09f4..7026d277 100644 --- a/examples/companion_radio/ui-new/RingtoneEditorScreen.h +++ b/examples/companion_radio/ui-new/RingtoneEditorScreen.h @@ -6,12 +6,11 @@ class RingtoneEditorScreen : public UIScreen { UITask* _task; NodePrefs* _prefs; - static const int MAX_NOTES = 32; - static const int VISIBLE_NOTES = 7; - static const int CELL_W = 18; - static const int NOTES_Y = 12; - static const int CELL_H = 14; - static const int MENU_VISIBLE = 5; + 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() static const uint16_t BPM_OPTS[5]; static const uint8_t DUR_VALS[4]; @@ -43,8 +42,8 @@ class RingtoneEditorScreen : public UIScreen { } void clampScroll() { - if (_cursor < _scroll) _scroll = _cursor; - if (_cursor >= _scroll + VISIBLE_NOTES) _scroll = _cursor - VISIBLE_NOTES + 1; + if (_cursor < _scroll) _scroll = _cursor; + if (_cursor >= _scroll + _visible_notes) _scroll = _cursor - _visible_notes + 1; if (_scroll < 0) _scroll = 0; } @@ -93,13 +92,18 @@ public: display.setTextSize(1); display.setColor(DisplayDriver::LIGHT); + const int lh = display.getLineHeight(); + const int notes_y = display.listStart(); + const int cell_h = lh + 6; + _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); display.setCursor(0, 0); display.print(hdr); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, display.headerH() - 1, display.width(), 1); - for (int i = 0; i < VISIBLE_NOTES; i++) { + for (int i = 0; i < _visible_notes; i++) { int ni = _scroll + i; int cx = i * CELL_W; bool sel = (ni == _cursor); @@ -116,50 +120,52 @@ 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 + 3, 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 + 6, notes_y + 3); display.print("+"); display.setColor(DisplayDriver::LIGHT); } } - int info_y = NOTES_Y + CELL_H + 2; + const int info_y = notes_y + cell_h + 2; + const int bottom_y = display.height() - display.lineStep(); if (_scroll > 0) { display.setCursor(0, info_y); display.print("<"); } - if (_scroll + VISIBLE_NOTES <= _len) { display.setCursor(display.width() - 6, info_y); display.print(">"); } + if (_scroll + _visible_notes <= _len) { display.setCursor(display.width() - display.getCharWidth(), info_y); display.print(">"); } if (_cursor < _len) { char info[24]; snprintf(info, sizeof(info), "oct:%d dur:%s", noteOctave(_notes[_cursor]), DUR_LABELS[noteDurIdx(_notes[_cursor])]); - display.setCursor(10, info_y); + display.setCursor(display.getCharWidth() + 2, info_y); display.print(info); } else if (_cursor == _len) { - display.setCursor(10, info_y); + display.setCursor(display.getCharWidth() + 2, info_y); display.print("U/D to add note"); } - display.setCursor(0, 54); + display.setCursor(0, bottom_y); display.print("ENT:oct MENU:opts"); if (_menu_open) { - static const int mw = 100, mh = MENU_VISIBLE * 10 + 4; - int mx = (128 - mw) / 2; - int my = (64 - mh) / 2; + const int menu_ih = display.lineStep(); + const int mw = 100, mh = MENU_VISIBLE * menu_ih + 4; + int mx = (display.width() - mw) / 2; + int my = (display.height() - mh) / 2; display.setColor(DisplayDriver::DARK); display.fillRect(mx, my, mw, mh); display.setColor(DisplayDriver::LIGHT); @@ -167,10 +173,10 @@ public: for (int i = 0; i < MENU_VISIBLE; i++) { int item = _menu_scroll + i; if (item >= M_COUNT) break; - int iy = my + 2 + i * 10; + int iy = my + 2 + i * menu_ih; bool sel = (item == _menu_sel); if (sel) { - display.fillRect(mx + 1, iy - 1, mw - 2, 10); + display.fillRect(mx + 1, iy - 1, mw - 2, menu_ih); display.setColor(DisplayDriver::DARK); } display.setCursor(mx + 4, iy); @@ -182,8 +188,9 @@ public: display.print(MENU_LABELS[item]); display.setColor(DisplayDriver::LIGHT); } - if (_menu_scroll > 0) { display.setCursor(mx + mw - 7, my + 2); display.print("^"); } - if (_menu_scroll + MENU_VISIBLE < M_COUNT) { display.setCursor(mx + mw - 7, my + mh - 10); display.print("v"); } + int cw = display.getCharWidth(); + if (_menu_scroll > 0) { display.setCursor(mx + mw - cw - 1, my + 2); display.print("^"); } + if (_menu_scroll + MENU_VISIBLE < M_COUNT) { display.setCursor(mx + mw - cw - 1, my + mh - menu_ih); display.print("v"); } } return 200; diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index 2c18ddf3..d848d2a1 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -57,13 +57,9 @@ class SettingsScreen : public UIScreen { Count }; - static const int VISIBLE_ITEMS = 4; - static const int ITEM_H = 11; - static const int START_Y = 12; - static const int VAL_X = 80; - int _selected; int _scroll; + int _visible; // items fitting on screen; updated each render, used by handleInput bool _dirty; static const uint16_t AUTO_OFF_OPTS[5]; @@ -230,7 +226,7 @@ class SettingsScreen : public UIScreen { if (sel) { display.setColor(DisplayDriver::LIGHT); - display.fillRect(0, y - 1, display.width(), ITEM_H); + display.fillRect(0, y - 1, display.width(), display.lineStep()); display.setColor(DisplayDriver::DARK); } else { display.setColor(DisplayDriver::LIGHT); @@ -242,10 +238,10 @@ class SettingsScreen : public UIScreen { if (item == BRIGHTNESS) { display.print("Bright"); - renderBar(display, VAL_X, y, (p ? p->display_brightness : 2) + 1, 5); + renderBar(display, display.valCol(), y, (p ? p->display_brightness : 2) + 1, 5); } else if (item == BUZZER) { display.print("Buzzer"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); #ifdef PIN_BUZZER { static const char* labels[] = { "ON", "OFF", "Auto" }; int m = _task->getBuzzerMode(); @@ -256,45 +252,45 @@ class SettingsScreen : public UIScreen { } else if (item == BUZZER_VOLUME) { display.print("BzrVol"); #ifdef PIN_BUZZER - renderBar(display, VAL_X, y, _task->getBuzzerVolume() + 1, 5); + renderBar(display, display.valCol(), y, _task->getBuzzerVolume() + 1, 5); #else - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print("N/A"); #endif } else if (item == DM_MELODY) { display.print("DM sound"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); { static const char* L[] = { "built-in", "M1", "M2" }; uint8_t v = p ? p->notif_melody_dm : 0; display.print(L[v < 3 ? v : 0]); } } else if (item == CH_MELODY) { display.print("Ch sound"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); { static const char* L[] = { "built-in", "M1", "M2" }; uint8_t v = p ? p->notif_melody_ch : 0; display.print(L[v < 3 ? v : 0]); } } else if (isHomePage(item)) { display.print(homePageLabel(item)); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print(homePageVisible(item, p) ? "ON" : "OFF"); } else if (item == TX_POWER) { display.print("TX Pwr"); char buf[8]; sprintf(buf, "%ddBm", p ? p->tx_power_dbm : 0); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print(buf); } else if (item == AUTO_OFF) { display.print("AutoOff"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print(AUTO_OFF_LABELS[autoOffIndex()]); } else if (item == AUTO_LOCK) { display.print("AutoLock"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print((p && p->auto_lock) ? "ON" : "OFF"); #if ENV_INCLUDE_GPS == 1 } else if (item == GPS_INTERVAL) { display.print("GPS upd"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print(GPS_INTERVAL_LABELS[gpsIntervalIndex()]); #endif } else if (item == TIMEZONE) { @@ -303,46 +299,46 @@ class SettingsScreen : public UIScreen { int8_t tz = p ? p->tz_offset_hours : 0; if (tz >= 0) sprintf(buf, "UTC+%d", (int)tz); else sprintf(buf, "UTC%d", (int)tz); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print(buf); } else if (item == LOW_BAT) { display.print("LowBat"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print(LOW_BAT_LABELS[lowBatIndex()]); } else if (item == BATT_DISPLAY) { display.print("BattDisp"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); uint8_t mode = p ? p->batt_display_mode : 0; display.print(BATT_DISPLAY_LABELS[mode < BATT_DISPLAY_COUNT ? mode : 0]); #if !defined(EINK_DISPLAY_MODEL) } else if (item == CLOCK_SECONDS) { display.print("Seconds"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print((p && p->clock_hide_seconds) ? "OFF" : "ON"); #endif } else if (item == CLOCK_FORMAT) { display.print("Format"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print((p && p->clock_12h) ? "12h" : "24h"); } else if (item == FONT) { display.print("Font"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print((p && p->use_lemon_font) ? "Lemon" : "Default"); #if defined(EINK_DISPLAY_MODEL) } else if (item == ROTATION) { display.print("Rotation"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); { static const char* ROT_LABELS[] = { "0deg", "90deg", "180deg", "270deg" }; uint8_t r = p ? (p->display_rotation & 3) : 0; display.print(ROT_LABELS[r]); } #endif } else if (item == DM_FILTER) { display.print("DM"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print((p && p->dm_show_all) ? "all" : "fav"); } else if (item == ROOM_FILTER) { display.print("Rooms"); - display.setCursor(VAL_X, y); + display.setCursor(display.valCol(), y); display.print((p && p->room_fav_only) ? "fav" : "all"); } else if (isMsgSlot(item)) { int slot = msgSlotIndex(item); @@ -350,7 +346,8 @@ class SettingsScreen : public UIScreen { snprintf(label, sizeof(label), "Q%d:", slot + 1); display.print(label); const char* tmpl = (p && p->custom_msgs[slot][0]) ? p->custom_msgs[slot] : "(empty)"; - display.drawTextEllipsized(VAL_X - 20, y, display.width() - VAL_X + 18, tmpl); + int xm = 8 + display.getCharWidth() * 4; + display.drawTextEllipsized(xm, y, display.width() - xm, tmpl); } } @@ -360,7 +357,7 @@ class SettingsScreen : public UIScreen { public: SettingsScreen(UITask* task) - : _task(task), _selected(BRIGHTNESS), _scroll(0), _dirty(false), _edit_slot(-1) {} + : _task(task), _selected(BRIGHTNESS), _scroll(0), _visible(4), _dirty(false), _edit_slot(-1) {} void markClean() { _dirty = false; } @@ -372,23 +369,27 @@ public: return _kb.render(display); } + int item_h = display.lineStep(); + int start_y = display.listStart(); + _visible = display.listVisible(item_h); + display.setColor(DisplayDriver::LIGHT); display.drawTextCentered(display.width() / 2, 0, "SETTINGS"); - display.fillRect(0, 10, display.width(), 1); + display.fillRect(0, display.headerH() - 2, display.width(), 1); - for (int i = 0; i < VISIBLE_ITEMS && (_scroll + i) < SettingItem::Count; i++) { - renderItem(display, _scroll + i, START_Y + i * ITEM_H); + for (int i = 0; i < _visible && (_scroll + i) < SettingItem::Count; i++) { + renderItem(display, _scroll + i, start_y + i * item_h); } // scroll indicators if (_scroll > 0) { display.setColor(DisplayDriver::LIGHT); - display.setCursor(display.width() - 6, START_Y); + display.setCursor(display.width() - display.getCharWidth(), start_y); display.print("^"); } - if (_scroll + VISIBLE_ITEMS < SettingItem::Count) { + if (_scroll + _visible < SettingItem::Count) { display.setColor(DisplayDriver::LIGHT); - display.setCursor(display.width() - 6, START_Y + (VISIBLE_ITEMS - 1) * ITEM_H); + display.setCursor(display.width() - display.getCharWidth(), start_y + (_visible - 1) * item_h); display.print("v"); } @@ -426,7 +427,7 @@ public: int next = nextSelectable(_selected); if (next != _selected) { _selected = next; - if (_selected >= _scroll + VISIBLE_ITEMS) _scroll = _selected - VISIBLE_ITEMS + 1; + if (_selected >= _scroll + _visible) _scroll = _selected - _visible + 1; } return true; } diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 01b1620f..f72b4afd 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -65,27 +65,36 @@ public: } int render(DisplayDriver& display) override { + display.setTextSize(1); + const int lh = display.getLineHeight(); + const int step = display.lineStep(); + // meshcore logo display.setColor(DisplayDriver::LIGHT); int logoWidth = 128; - display.drawXbm((display.width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); + int logo_y = 3; + display.drawXbm((display.width() - logoWidth) / 2, logo_y, meshcore_logo, logoWidth, 13); - // version info + // version info at sz2 + int ver_y = logo_y + 13 + 2; display.setTextSize(2); - display.drawTextCentered(display.width()/2, 22, _version_info); + 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; display.setTextSize(1); - display.drawTextCentered(display.width()/2, 42, FIRMWARE_BUILD_DATE); + display.drawTextCentered(display.width()/2, date_y, FIRMWARE_BUILD_DATE); #ifdef FIRMWARE_PLUS_BUILD - display.fillRect(0, 53, display.width(), 10); + int plus_y = date_y + step; + display.fillRect(0, plus_y - 1, display.width(), lh + 2); display.setColor(DisplayDriver::DARK); char plus_label[24]; if (_plus_ver[0]) snprintf(plus_label, sizeof(plus_label), "Plus %s for Wio", _plus_ver); else snprintf(plus_label, sizeof(plus_label), "Plus for Wio"); - display.drawTextCentered(display.width()/2, 54, plus_label); + display.drawTextCentered(display.width()/2, plus_y, plus_label); display.setColor(DisplayDriver::LIGHT); #endif @@ -338,9 +347,14 @@ public: int render(DisplayDriver& display) override { char tmp[80]; + display.setTextSize(1); + const int lh = display.getLineHeight(); // line height at sz1 + const int step = display.lineStep(); // lh + 2 + const int dots_y = lh + 4; // page-dot row: just below header + const int content_y = dots_y + step; // first content row + // node name + battery — hidden on CLOCK page (full screen used for dashboard) if (_page != CLOCK) { - display.setTextSize(1); display.setColor(DisplayDriver::LIGHT); char filtered_name[sizeof(_node_prefs->node_name)]; display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name)); @@ -360,13 +374,12 @@ public: if (i == _page) curr_vis = vis_count; vis_count++; } - int y = 14; int x = display.width() / 2 - 5 * (vis_count - 1); int vi = 0; for (int i = 0; i < (int)Count; i++) { if (!isPageVisible(i)) continue; - if (vi == curr_vis) display.fillRect(x-1, y-1, 3, 3); - else display.fillRect(x, y, 1, 1); + if (vi == curr_vis) display.fillRect(x-1, dots_y-1, 3, 3); + else display.fillRect(x, dots_y, 1, 1); x += 10; vi++; } } @@ -376,9 +389,10 @@ public: if (unix_ts < 1000000000UL) { display.setColor(DisplayDriver::LIGHT); display.setTextSize(1); - display.drawTextCentered(display.width() / 2, 25, "! No time sync"); - display.drawTextCentered(display.width() / 2, 40, "Enable GPS or"); - display.drawTextCentered(display.width() / 2, 51, "connect app"); + int mid_y = display.height() / 2 - step; + display.drawTextCentered(display.width() / 2, mid_y, "! No time sync"); + display.drawTextCentered(display.width() / 2, mid_y + step, "Enable GPS or"); + display.drawTextCentered(display.width() / 2, mid_y + step * 2, "connect app"); } else { int8_t tz = _node_prefs ? _node_prefs->tz_offset_hours : 0; unix_ts += (int32_t)tz * 3600; @@ -407,12 +421,14 @@ public: 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); - display.fillRect(0, 28, display.width(), 1); + int sep_y = 19 + lh + 1; + int dash0 = sep_y + 3; + display.fillRect(0, sep_y, display.width(), 1); // dashboard data fields if (_node_prefs) { refresh_sensors(); - static const int FIELD_Y[] = { 31, 41, 51 }; + 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 == DASH_NONE) continue; @@ -491,8 +507,8 @@ public: } else if (_page == HomePage::RECENT) { the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE); display.setColor(DisplayDriver::LIGHT); - int y = 20; - for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) { + int y = content_y; + for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += step) { auto a = &recent[i]; if (a->name[0] == 0) continue; // empty slot int secs = _rtc->getCurrentTime() - a->recv_timestamp; @@ -515,44 +531,46 @@ public: } } else if (_page == HomePage::RADIO) { display.setColor(DisplayDriver::LIGHT); - display.setTextSize(1); // freq / sf - display.setCursor(0, 20); + display.setCursor(0, content_y); sprintf(tmp, "FQ: %06.3f SF: %d", _node_prefs->freq, _node_prefs->sf); display.print(tmp); - display.setCursor(0, 31); + display.setCursor(0, content_y + step); sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr); display.print(tmp); - // tx power, noise floor - display.setCursor(0, 42); + // tx power, noise floor + display.setCursor(0, content_y + step * 2); sprintf(tmp, "TX: %ddBm", _node_prefs->tx_power_dbm); display.print(tmp); - display.setCursor(0, 53); + display.setCursor(0, content_y + step * 3); sprintf(tmp, "Noise floor: %d", radio_driver.getNoiseFloor()); display.print(tmp); } else if (_page == HomePage::BLUETOOTH) { display.setColor(DisplayDriver::LIGHT); - display.drawXbm((display.width() - 32) / 2, 14, + int icon_y = content_y; + display.drawXbm((display.width() - 32) / 2, icon_y, _task->isSerialEnabled() ? bluetooth_on : bluetooth_off, 32, 32); display.setTextSize(1); + int pin_y = icon_y + 32 + 3; + int hint_y = pin_y + step; if (_task->isSerialEnabled() && !_task->hasConnection() && the_mesh.getBLEPin() != 0) { char pin_buf[16]; snprintf(pin_buf, sizeof(pin_buf), "PIN: %d", the_mesh.getBLEPin()); - display.drawTextCentered(display.width() / 2, 49, pin_buf); + display.drawTextCentered(display.width() / 2, pin_y, pin_buf); } - display.drawTextCentered(display.width() / 2, 57, "toggle: " PRESS_LABEL); + display.drawTextCentered(display.width() / 2, hint_y, "toggle: " PRESS_LABEL); } else if (_page == HomePage::ADVERT) { display.setColor(DisplayDriver::LIGHT); - display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32); - display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL); + display.drawXbm((display.width() - 32) / 2, content_y, advert_icon, 32, 32); + display.drawTextCentered(display.width() / 2, content_y + 32 + 3, "advert: " PRESS_LABEL); #if ENV_INCLUDE_GPS == 1 } else if (_page == HomePage::GPS) { LocationProvider* nmea = sensors.getLocationProvider(); char buf[50]; - int y = 18; + int y = content_y; bool gps_state = _task->getGPSState(); #ifdef PIN_GPS_SWITCH bool hw_gps_state = digitalRead(PIN_GPS_SWITCH); @@ -566,30 +584,30 @@ public: #endif display.drawTextLeftAlign(0, y, buf); if (nmea == NULL) { - y = y + 12; + y += step; display.drawTextLeftAlign(0, y, "Can't access GPS"); } else { strcpy(buf, nmea->isValid()?"fix":"no fix"); display.drawTextRightAlign(display.width()-1, y, buf); - y = y + 12; + y += step; display.drawTextLeftAlign(0, y, "sat"); sprintf(buf, "%d", nmea->satellitesCount()); display.drawTextRightAlign(display.width()-1, y, buf); - y = y + 12; + y += step; display.drawTextLeftAlign(0, y, "pos"); - sprintf(buf, "%.4f %.4f", + sprintf(buf, "%.4f %.4f", nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.); display.drawTextRightAlign(display.width()-1, y, buf); - y = y + 12; + y += step; display.drawTextLeftAlign(0, y, "alt"); sprintf(buf, "%.2f", nmea->getAltitude()/1000.); display.drawTextRightAlign(display.width()-1, y, buf); - y = y + 12; + y += step; } #endif #if UI_SENSORS_PAGE == 1 } else if (_page == HomePage::SENSORS) { - int y = 18; + int y = content_y; refresh_sensors(); uint8_t avail_types[16]; @@ -655,7 +673,7 @@ public: display.print(name); display.setCursor(display.width() - display.getTextWidth(buf) - 1, y); display.print(buf); - y += 12; + y += step; } if (need_scroll) sensors_scroll_offset = (sensors_scroll_offset + 1) % avail_count; else sensors_scroll_offset = 0; @@ -663,32 +681,32 @@ public: } else if (_page == HomePage::SETTINGS) { display.setColor(DisplayDriver::LIGHT); display.setTextSize(1); - display.drawTextCentered(display.width() / 2, 22, "Settings"); - display.drawTextCentered(display.width() / 2, 50, PRESS_LABEL " to open"); + display.drawTextCentered(display.width() / 2, content_y, "Settings"); + display.drawTextCentered(display.width() / 2, content_y + step * 2, PRESS_LABEL " to open"); } else if (_page == HomePage::TOOLS) { display.setColor(DisplayDriver::LIGHT); display.setTextSize(1); - display.drawTextCentered(display.width() / 2, 22, "Tools"); - display.drawTextCentered(display.width() / 2, 50, PRESS_LABEL " to open"); + display.drawTextCentered(display.width() / 2, content_y, "Tools"); + display.drawTextCentered(display.width() / 2, content_y + step * 2, PRESS_LABEL " to open"); } else if (_page == HomePage::QUICK_MSG) { display.setColor(DisplayDriver::LIGHT); display.setTextSize(1); - display.drawTextCentered(display.width() / 2, 22, "Messages"); + display.drawTextCentered(display.width() / 2, content_y, "Messages"); int total_unread = _task->getDMUnreadTotal() + _task->getChannelUnreadCount() + _task->getRoomUnreadCount(); if (total_unread > 0) { char badge[20]; snprintf(badge, sizeof(badge), "%d unread", total_unread); - display.drawTextCentered(display.width() / 2, 35, badge); + display.drawTextCentered(display.width() / 2, content_y + step, badge); } - display.drawTextCentered(display.width() / 2, 50, PRESS_LABEL " to open"); + display.drawTextCentered(display.width() / 2, content_y + step * 2, PRESS_LABEL " to open"); } else if (_page == HomePage::SHUTDOWN) { display.setColor(DisplayDriver::LIGHT); display.setTextSize(1); if (_shutdown_init) { - display.drawTextCentered(display.width() / 2, 34, "hibernating..."); + display.drawTextCentered(display.width() / 2, content_y + step, "hibernating..."); } else { - display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32); - display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL); + display.drawXbm((display.width() - 32) / 2, content_y, power_icon, 32, 32); + display.drawTextCentered(display.width() / 2, content_y + 32 + 3, "hibernate:" PRESS_LABEL); } } bool auto_adv = _node_prefs && _node_prefs->advert_auto_interval_sec > 0; @@ -1337,9 +1355,11 @@ void UITask::loop() { // Lock screen: clock + unlock hint popup uint32_t unix_ts = rtc_clock.getCurrentTime(); _display->setColor(DisplayDriver::LIGHT); + _display->setTextSize(1); + const int lk_lh = _display->getLineHeight(); + const int lk_step = _display->lineStep(); if (unix_ts < 1000000000UL) { - _display->setTextSize(1); - _display->drawTextCentered(_display->width() / 2, 20, "No time sync"); + _display->drawTextCentered(_display->width() / 2, _display->height() / 2 - lk_step, "No time sync"); } else { int8_t tz = _node_prefs ? _node_prefs->tz_offset_hours : 0; unix_ts += (int32_t)tz * 3600; @@ -1347,18 +1367,21 @@ void UITask::loop() { struct tm* ti = gmtime(&t); char buf[12]; _display->setTextSize(2); + const int lh2 = _display->getLineHeight(); // sz2 line height + const int clk_y = 2; if (_node_prefs && _node_prefs->clock_12h) { int h = ti->tm_hour % 12; if (h == 0) h = 12; sprintf(buf, "%d:%02d %s", h, ti->tm_min, ti->tm_hour < 12 ? "AM" : "PM"); } else { sprintf(buf, "%02d:%02d", ti->tm_hour, ti->tm_min); } - _display->drawTextCentered(_display->width() / 2, 8, buf); + _display->drawTextCentered(_display->width() / 2, clk_y, 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", wd[ti->tm_wday], ti->tm_mday, mo[ti->tm_mon]); - _display->drawTextCentered(_display->width() / 2, 26, buf); + int date_y = clk_y + lh2 + 2; + _display->drawTextCentered(_display->width() / 2, date_y, buf); // Two sensor values side by side (dashboard_fields[0] and [1]) if (_node_prefs) { @@ -1375,17 +1398,17 @@ void UITask::loop() { formatDashVal(f0, v0, sizeof(v0), _batt_mv, lpp_ptr); formatDashVal(f1, v1, sizeof(v1), _batt_mv, lpp_ptr); if (v0[0] || v1[0]) { - _display->setTextSize(1); + int sv_y = date_y + lk_step; _display->setColor(DisplayDriver::LIGHT); if (v0[0] && v1[0]) { - _display->setCursor(0, 37); + _display->setCursor(0, sv_y); _display->print(v0); int vw = _display->getTextWidth(v1); - _display->setCursor(_display->width() - vw, 37); + _display->setCursor(_display->width() - vw, sv_y); _display->print(v1); } else { const char* sv = v0[0] ? v0 : v1; - _display->drawTextCentered(_display->width() / 2, 37, sv); + _display->drawTextCentered(_display->width() / 2, sv_y, sv); } } } @@ -1395,11 +1418,11 @@ void UITask::loop() { const char* hint = _lock_seq_count == 0 ? "Hold Back + 3xEnter" : _lock_seq_count == 1 ? "Enter x2 more..." : "Enter x1 more..."; int p = 3; - int hy = _display->height() - 13; + int hy = _display->height() - lk_lh - p * 2; int hw = _display->getTextWidth(hint); int hx = (_display->width() - hw) / 2; _display->setColor(DisplayDriver::LIGHT); - _display->fillRect(hx - p, hy - p, hw + p*2, 8 + p*2); + _display->fillRect(hx - p, hy - p, hw + p*2, lk_lh + p*2); _display->setColor(DisplayDriver::DARK); _display->setCursor(hx, hy); _display->print(hint); @@ -1457,8 +1480,10 @@ void UITask::loop() { _display->startFrame(); _display->setTextSize(1); _display->setColor(DisplayDriver::LIGHT); - _display->drawTextCentered(_display->width() / 2, 24, "Low Battery"); - _display->drawTextCentered(_display->width() / 2, 36, "Shutting down"); + int mid = _display->height() / 2; + int step = _display->lineStep(); + _display->drawTextCentered(_display->width() / 2, mid - step, "Low Battery"); + _display->drawTextCentered(_display->width() / 2, mid, "Shutting down"); _display->endFrame(); delay(2000); } diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index d272a9db..c75baed9 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -31,6 +31,16 @@ public: virtual int getCharWidth() const { return 6; } // typical character advance width (px) virtual int getLineHeight() const { return 8; } // pixel rows per text line virtual void setLemonFont(bool) { } // no-op; overridden by displays that support Lemon + + // Layout helpers — derived from font metrics and screen size. + // Use these instead of hardcoded pixel values so layouts adapt to any display. + int lineStep() const { return getLineHeight() + 2; } // row pitch: text + gap + int headerH() const { return getLineHeight() + 3; } // title bar height + int listStart() const { return headerH(); } // y where list items begin + int listVisible(int itemH) const { return (height() - listStart()) / itemH; } + int listVisible() const { return listVisible(lineStep()); } + // x where a right-side value column starts (leaves ~8 chars for the value) + int valCol() const { return width() - getCharWidth() * 8; } virtual void drawTextCentered(int mid_x, int y, const char* str) { // helper method (override to optimise) int w = getTextWidth(str); setCursor(mid_x - w/2, y);