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);