mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
feat(l1): one title bar, page dots, disclosure marks and push buttons
drawScreenHeader replaces drawCenteredHeader on every ui-new screen: the title in normal case, page dots instead of "TRAIL 1/3", the menu hint, no icon (the screen was just picked by one). Accordions in Settings and Tools show a disclosure triangle instead of +/-, Tools sections drop their icons. Form "[Save]" rows and "+ Send" share drawButton; form fields use valueRow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -178,7 +178,7 @@ public:
|
|||||||
if (_login_kb) return kb().render(display);
|
if (_login_kb) return kb().render(display);
|
||||||
AdminSession::State st = S().state();
|
AdminSession::State st = S().state();
|
||||||
if (st == AdminSession::LOGGING_IN || st == AdminSession::IDLE || st == AdminSession::NEED_PASSWORD) {
|
if (st == AdminSession::LOGGING_IN || st == AdminSession::IDLE || st == AdminSession::NEED_PASSWORD) {
|
||||||
display.drawCenteredHeader("ADMIN LOGIN");
|
drawScreenHeader(display, "Admin login");
|
||||||
if (st != AdminSession::LOGGING_IN) return 500;
|
if (st != AdminSession::LOGGING_IN) return 500;
|
||||||
return waiting(display, "Logging in");
|
return waiting(display, "Logging in");
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ public:
|
|||||||
if (st == AdminSession::WAITING) {
|
if (st == AdminSession::WAITING) {
|
||||||
char title[24];
|
char title[24];
|
||||||
snprintf(title, sizeof(title), "%.23s", S().target().name);
|
snprintf(title, sizeof(title), "%.23s", S().target().name);
|
||||||
display.drawCenteredHeader(title);
|
drawScreenHeader(display, title);
|
||||||
return waiting(display, S().fetching() ? "Fetching" : "Waiting for reply");
|
return waiting(display, S().fetching() ? "Fetching" : "Waiting for reply");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public:
|
|||||||
int render(DisplayDriver& display) override {
|
int render(DisplayDriver& display) override {
|
||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.drawCenteredHeader("AUTO-ADVERT");
|
drawScreenHeader(display, "Auto-advert");
|
||||||
int sel = 0, scroll = 0;
|
int sel = 0, scroll = 0;
|
||||||
drawList(display, 1, sel, scroll, [&](int, int y, bool is_sel, int reserve) {
|
drawList(display, 1, sel, scroll, [&](int, int y, bool is_sel, int reserve) {
|
||||||
drawRowSelection(display, y, is_sel, reserve);
|
drawRowSelection(display, y, is_sel, reserve);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
// existing CMD_GET_CHANNEL comment ("NOTE: only 128-bit supported").
|
// existing CMD_GET_CHANNEL comment ("NOTE: only 128-bit supported").
|
||||||
|
|
||||||
#include "../ui-core/ChannelControl.h"
|
#include "../ui-core/ChannelControl.h"
|
||||||
|
#include "InfoKit.h"
|
||||||
|
|
||||||
class ChannelsView {
|
class ChannelsView {
|
||||||
UITask* _task;
|
UITask* _task;
|
||||||
@@ -45,7 +46,7 @@ class ChannelsView {
|
|||||||
char _secret_text[33] = ""; // passphrase or hex string typed so far
|
char _secret_text[33] = ""; // passphrase or hex string typed so far
|
||||||
char _topic[31] = ""; // ADD_HASHTAG: topic without the leading '#'
|
char _topic[31] = ""; // ADD_HASHTAG: topic without the leading '#'
|
||||||
|
|
||||||
int _sel = 0; // 0=Name 1=Secret 2=[Save] (ADD_HASHTAG: 0=Topic 1=[Save])
|
int _sel = 0; // 0=Name 1=Secret 2=Save (ADD_HASHTAG: 0=Topic 1=Save)
|
||||||
bool _kb_active = false;
|
bool _kb_active = false;
|
||||||
int _kb_field = -1; // 0=Name, 1=Secret, 2=Topic
|
int _kb_field = -1; // 0=Name, 1=Secret, 2=Topic
|
||||||
|
|
||||||
@@ -117,7 +118,7 @@ public:
|
|||||||
const int step = display.lineStep();
|
const int step = display.lineStep();
|
||||||
|
|
||||||
if (_mode == TYPE_PICK) {
|
if (_mode == TYPE_PICK) {
|
||||||
display.drawCenteredHeader("ADD CHANNEL");
|
drawScreenHeader(display, "Add channel");
|
||||||
static const char* TYPE_LABELS[3] = { "Public", "Hashtag", "Private" };
|
static const char* TYPE_LABELS[3] = { "Public", "Hashtag", "Private" };
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
int y = top + i * step;
|
int y = top + i * step;
|
||||||
@@ -129,37 +130,33 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_mode == ADD_HASHTAG) {
|
if (_mode == ADD_HASHTAG) {
|
||||||
display.drawCenteredHeader("ADD CHANNEL");
|
drawScreenHeader(display, "Add channel");
|
||||||
int mq_delay = 0;
|
int mq_delay = 0;
|
||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
int y = top + i * step;
|
int y = top + i * step;
|
||||||
bool sel = (i == _sel);
|
bool sel = (i == _sel);
|
||||||
|
if (i == 1) { drawButton(display, (display.width() - buttonWidth(display, "Save")) / 2, y, "Save", sel); continue; }
|
||||||
display.drawSelectionRow(0, y - 1, display.width(), step - 1, sel);
|
display.drawSelectionRow(0, y - 1, display.width(), step - 1, sel);
|
||||||
char row[40];
|
int r = info::valueRow(display, y, "Topic", _topic[0] ? _topic : "(none)", sel, 0);
|
||||||
if (i == 0) snprintf(row, sizeof(row), "Topic: %s", _topic[0] ? _topic : "(none)");
|
if (r > 0) mq_delay = r;
|
||||||
else snprintf(row, sizeof(row), "[Save]");
|
|
||||||
int r = display.drawTextEllipsized(2, y, display.width() - 4, row, sel);
|
|
||||||
if (sel && r > 0) mq_delay = r;
|
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
}
|
}
|
||||||
return (mq_delay > 0 && mq_delay < 1000) ? mq_delay : 1000;
|
return (mq_delay > 0 && mq_delay < 1000) ? mq_delay : 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.drawCenteredHeader(_mode == ADD ? "ADD CHANNEL" : "EDIT CHANNEL");
|
drawScreenHeader(display, _mode == ADD ? "Add channel" : "Edit channel");
|
||||||
int mq_delay = 0;
|
int mq_delay = 0;
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
int y = top + i * step;
|
int y = top + i * step;
|
||||||
bool sel = (i == _sel);
|
bool sel = (i == _sel);
|
||||||
|
if (i == 2) { drawButton(display, (display.width() - buttonWidth(display, "Save")) / 2, y, "Save", sel); continue; }
|
||||||
display.drawSelectionRow(0, y - 1, display.width(), step - 1, sel);
|
display.drawSelectionRow(0, y - 1, display.width(), step - 1, sel);
|
||||||
char row[40];
|
// valueRow cuts a long name/secret (scrolling it while selected) rather
|
||||||
if (i == 0) snprintf(row, sizeof(row), "Name: %s", _name[0] ? _name : "(none)");
|
// than letting print() wrap it onto the next row's line.
|
||||||
else if (i == 1) snprintf(row, sizeof(row), "Secret (%s): %s",
|
int r = i == 0 ? info::valueRow(display, y, "Name", _name[0] ? _name : "(none)", sel, 0)
|
||||||
_hex_mode ? "hex" : "phrase", _secret_text[0] ? _secret_text : "(none)");
|
: info::valueRow(display, y, _hex_mode ? "Secret (hex)" : "Secret (phrase)",
|
||||||
else snprintf(row, sizeof(row), "[Save]");
|
_secret_text[0] ? _secret_text : "(none)", sel, 0);
|
||||||
// Ellipsize rather than print() directly -- a long name/secret must not
|
if (r > 0) mq_delay = r;
|
||||||
// wrap onto the next row's line (print() wraps by default).
|
|
||||||
int r = display.drawTextEllipsized(2, y, display.width() - 4, row, sel);
|
|
||||||
if (sel && r > 0) mq_delay = r;
|
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
}
|
}
|
||||||
return (mq_delay > 0 && mq_delay < 1000) ? mq_delay : 1000;
|
return (mq_delay > 0 && mq_delay < 1000) ? mq_delay : 1000;
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class ClockToolsScreen : public UIScreen {
|
|||||||
|
|
||||||
// ── Sub-renders ───────────────────────────────────────────────────────────
|
// ── Sub-renders ───────────────────────────────────────────────────────────
|
||||||
int renderMenu(DisplayDriver& d) {
|
int renderMenu(DisplayDriver& d) {
|
||||||
d.drawCenteredHeader("CLOCK TOOLS");
|
drawScreenHeader(d, "Clock tools");
|
||||||
static const char* items[3] = { "Alarm", "Timer", "Stopwatch" };
|
static const char* items[3] = { "Alarm", "Timer", "Stopwatch" };
|
||||||
if (_sel > 2) _sel = 2;
|
if (_sel > 2) _sel = 2;
|
||||||
drawList(d, 3, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
|
drawList(d, 3, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
|
||||||
@@ -163,7 +163,7 @@ class ClockToolsScreen : public UIScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int renderAlarm(DisplayDriver& d) {
|
int renderAlarm(DisplayDriver& d) {
|
||||||
d.drawCenteredHeader("ALARM");
|
drawScreenHeader(d, "Alarm");
|
||||||
if (!_prefs) return 60000;
|
if (!_prefs) return 60000;
|
||||||
const char* rows[3] = { "Time", "Repeat", "Armed" };
|
const char* rows[3] = { "Time", "Repeat", "Armed" };
|
||||||
if (_sel > 2) _sel = 2;
|
if (_sel > 2) _sel = 2;
|
||||||
@@ -186,7 +186,7 @@ class ClockToolsScreen : public UIScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int renderTimer(DisplayDriver& d) {
|
int renderTimer(DisplayDriver& d) {
|
||||||
d.drawCenteredHeader("TIMER");
|
drawScreenHeader(d, "Timer");
|
||||||
const int cx = d.width() / 2;
|
const int cx = d.width() / 2;
|
||||||
if (_task->isTimerRunning()) {
|
if (_task->isTimerRunning()) {
|
||||||
char buf[16];
|
char buf[16];
|
||||||
@@ -215,7 +215,7 @@ class ClockToolsScreen : public UIScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int renderStopwatch(DisplayDriver& d) {
|
int renderStopwatch(DisplayDriver& d) {
|
||||||
d.drawCenteredHeader("STOPWATCH");
|
drawScreenHeader(d, "Stopwatch");
|
||||||
const int cx = d.width() / 2;
|
const int cx = d.width() / 2;
|
||||||
char buf[16];
|
char buf[16];
|
||||||
bool tenths = !d.isEink() && _sw_running; // tenths only on a fast panel
|
bool tenths = !d.isEink() && _sw_running; // tenths only on a fast panel
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public:
|
|||||||
int render(DisplayDriver& display) override {
|
int render(DisplayDriver& display) override {
|
||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.drawCenteredHeader("COMPASS");
|
drawScreenHeader(display, "Compass");
|
||||||
|
|
||||||
const int cx = display.width() / 2;
|
const int cx = display.width() / 2;
|
||||||
const int ch = display.getLineHeight();
|
const int ch = display.getLineHeight();
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public:
|
|||||||
int start_y = display.listStart();
|
int start_y = display.listStart();
|
||||||
int val_x = display.valCol();
|
int val_x = display.valCol();
|
||||||
|
|
||||||
display.drawCenteredHeader("CLOCK FIELDS");
|
drawScreenHeader(display, "Clock fields");
|
||||||
|
|
||||||
static const char* labels[] = { "Field 1", "Field 2", "Field 3" };
|
static const char* labels[] = { "Field 1", "Field 2", "Field 3" };
|
||||||
for (int i = 0; i < FIELD_SLOTS; i++) {
|
for (int i = 0; i < FIELD_SLOTS; i++) {
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ public:
|
|||||||
buildItems();
|
buildItems();
|
||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.drawCenteredHeader("GPIO");
|
drawScreenHeader(display, "GPIO");
|
||||||
|
|
||||||
bool any_live = false;
|
bool any_live = false;
|
||||||
drawList(display, _item_count, _sel, _scroll, [&](int row, int y, bool sel, int reserve) {
|
drawList(display, _item_count, _sel, _scroll, [&](int row, int y, bool sel, int reserve) {
|
||||||
|
|||||||
@@ -684,7 +684,7 @@ struct KeyboardWidget {
|
|||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.fillRect(0, chars_y, display.width(), hh);
|
display.fillRect(0, chars_y, display.width(), hh);
|
||||||
display.setColor(DisplayDriver::DARK);
|
display.setColor(DisplayDriver::DARK);
|
||||||
display.drawTextCentered(display.width() / 2, chars_y + 1, "CURSOR MODE");
|
display.drawTextCentered(display.width() / 2, chars_y + 1, "Cursor mode");
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.drawTextCentered(display.width() / 2, chars_y + hh + 2, "L/R move");
|
display.drawTextCentered(display.width() / 2, chars_y + hh + 2, "L/R move");
|
||||||
display.drawTextCentered(display.width() / 2, chars_y + hh + 2 + lh, "U/D start/end");
|
display.drawTextCentered(display.width() / 2, chars_y + hh + 2 + lh, "U/D start/end");
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public:
|
|||||||
int render(DisplayDriver& display) override {
|
int render(DisplayDriver& display) override {
|
||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.drawCenteredHeader("LIVE SHARE");
|
drawScreenHeader(display, "Live share");
|
||||||
|
|
||||||
int mq_delay = 0;
|
int mq_delay = 0;
|
||||||
drawList(display, ROW_COUNT, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
|
drawList(display, ROW_COUNT, _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ public:
|
|||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
if (_picking) { int mq = renderPicker(display); return (mq > 0 && mq < 400) ? mq : 400; }
|
if (_picking) { int mq = renderPicker(display); return (mq > 0 && mq < 400) ? mq : 400; }
|
||||||
display.drawCenteredHeader("LOCATOR");
|
drawScreenHeader(display, "Locator");
|
||||||
|
|
||||||
const int rc = visibleRows();
|
const int rc = visibleRows();
|
||||||
if (_sel >= rc) _sel = rc - 1; // beeper row may have just been hidden
|
if (_sel >= rc) _sel = rc - 1; // beeper row may have just been hidden
|
||||||
@@ -261,7 +261,7 @@ public:
|
|||||||
// Returns 0, or the ms until the selected row should next redraw to keep a
|
// Returns 0, or the ms until the selected row should next redraw to keep a
|
||||||
// marquee animation going (see DisplayDriver::drawTextEllipsized).
|
// marquee animation going (see DisplayDriver::drawTextEllipsized).
|
||||||
int renderPicker(DisplayDriver& display) {
|
int renderPicker(DisplayDriver& display) {
|
||||||
display.drawCenteredHeader("PICK TARGET");
|
drawScreenHeader(display, "Pick target");
|
||||||
uint32_t now = rtc_clock.getCurrentTime();
|
uint32_t now = rtc_clock.getCurrentTime();
|
||||||
int mq_delay = 0;
|
int mq_delay = 0;
|
||||||
drawList(display, _target_n, _pick_sel, _pick_scroll, [&](int i, int y, bool sel, int reserve) {
|
drawList(display, _target_n, _pick_sel, _pick_scroll, [&](int i, int y, bool sel, int reserve) {
|
||||||
|
|||||||
@@ -565,16 +565,9 @@ class MessagesScreen : public UIScreen {
|
|||||||
// reset to LIGHT first so it stays visible regardless of the ink the
|
// reset to LIGHT first so it stays visible regardless of the ink the
|
||||||
// message loop left.
|
// message loop left.
|
||||||
static void drawComposeButton(DisplayDriver& d, int cby, int lh, bool sel) {
|
static void drawComposeButton(DisplayDriver& d, int cby, int lh, bool sel) {
|
||||||
|
(void)lh;
|
||||||
const char* ctxt = "+ Send";
|
const char* ctxt = "+ Send";
|
||||||
int ctw = d.getTextWidth(ctxt);
|
drawButton(d, d.width() - buttonWidth(d, ctxt), cby, ctxt, sel);
|
||||||
int bw = ctw + 6;
|
|
||||||
int bx = d.width() - bw;
|
|
||||||
d.setColor(DisplayDriver::LIGHT);
|
|
||||||
if (sel) { d.fillSoftRect(bx, cby, bw, lh); d.setColor(DisplayDriver::DARK); }
|
|
||||||
else d.drawSoftRect(bx, cby, bw, lh);
|
|
||||||
d.setCursor(bx + 3, cby);
|
|
||||||
d.print(ctxt);
|
|
||||||
d.setColor(DisplayDriver::LIGHT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void afterSend(bool ok, const char* msg) {
|
void afterSend(bool ok, const char* msg) {
|
||||||
@@ -1206,11 +1199,11 @@ public:
|
|||||||
|
|
||||||
if (_phase == MODE_SELECT) {
|
if (_phase == MODE_SELECT) {
|
||||||
// Say which dial slot is being filled -- entered from the Favourites page,
|
// Say which dial slot is being filled -- entered from the Favourites page,
|
||||||
// a bare "MESSAGE" gives no sign that this browse is a pick.
|
// a bare "Messages" gives no sign that this browse is a pick.
|
||||||
char hdr[16];
|
char hdr[16];
|
||||||
if (_pick_fav_slot >= 0) snprintf(hdr, sizeof(hdr), "PIN SLOT %d", _pick_fav_slot + 1);
|
if (_pick_fav_slot >= 0) snprintf(hdr, sizeof(hdr), "Pin slot %d", _pick_fav_slot + 1);
|
||||||
else snprintf(hdr, sizeof(hdr), "MESSAGE");
|
else snprintf(hdr, sizeof(hdr), "Messages");
|
||||||
display.drawCenteredHeader(hdr, true, _ctx_menu.active);
|
drawScreenHeader(display, hdr, -1, 0, true, _ctx_menu.active);
|
||||||
const char* opts[] = { "Direct message", "Channels", "Room Servers" };
|
const char* opts[] = { "Direct message", "Channels", "Room Servers" };
|
||||||
int badges[3] = {
|
int badges[3] = {
|
||||||
getDMUnreadTotal(),
|
getDMUnreadTotal(),
|
||||||
@@ -1236,7 +1229,7 @@ public:
|
|||||||
if (_ctx_menu.active) { int r = _ctx_menu.render(display); if (mq_delay <= 0 || r < mq_delay) mq_delay = r; }
|
if (_ctx_menu.active) { int r = _ctx_menu.render(display); if (mq_delay <= 0 || r < mq_delay) mq_delay = r; }
|
||||||
|
|
||||||
} else if (_phase == CONTACT_PICK) {
|
} else if (_phase == CONTACT_PICK) {
|
||||||
display.drawCenteredHeader(_room_mode ? "SELECT ROOM" : "SELECT CONTACT", true, _ctx_menu.active);
|
drawScreenHeader(display, _room_mode ? "Rooms" : "Contacts", -1, 0, true, _ctx_menu.active);
|
||||||
|
|
||||||
if (_num_contacts == 0) {
|
if (_num_contacts == 0) {
|
||||||
display.drawTextCentered(display.width()/2, display.height()/2, _room_mode ? "No room servers" : "No favourites");
|
display.drawTextCentered(display.width()/2, display.height()/2, _room_mode ? "No room servers" : "No favourites");
|
||||||
@@ -1276,7 +1269,7 @@ public:
|
|||||||
if (_ctx_menu.active) { int r = _ctx_menu.render(display); if (mq_delay <= 0 || r < mq_delay) mq_delay = r; }
|
if (_ctx_menu.active) { int r = _ctx_menu.render(display); if (mq_delay <= 0 || r < mq_delay) mq_delay = r; }
|
||||||
|
|
||||||
} else if (_phase == CHANNEL_PICK) {
|
} else if (_phase == CHANNEL_PICK) {
|
||||||
display.drawCenteredHeader("SELECT CHANNEL", true, _ctx_menu.active);
|
drawScreenHeader(display, "Channels", -1, 0, true, _ctx_menu.active);
|
||||||
|
|
||||||
// "+ Add channel" is a synthetic trailing row — suppressed while picking
|
// "+ Add channel" is a synthetic trailing row — suppressed while picking
|
||||||
// a channel for the bot, so that picker's list stays unchanged.
|
// a channel for the bot, so that picker's list stays unchanged.
|
||||||
@@ -1360,7 +1353,7 @@ public:
|
|||||||
|
|
||||||
char title[24];
|
char title[24];
|
||||||
snprintf(title, sizeof(title), "%.23s", filtered_name);
|
snprintf(title, sizeof(title), "%.23s", filtered_name);
|
||||||
display.drawCenteredHeader(title, true, _ctx_menu.active);
|
drawScreenHeader(display, title, -1, 0, true, _ctx_menu.active);
|
||||||
|
|
||||||
int dm_count = _history.dmHistCountForContact(_sel_contact.id.pub_key);
|
int dm_count = _history.dmHistCountForContact(_sel_contact.id.pub_key);
|
||||||
uint32_t now_ts = rtc_clock.getCurrentTime();
|
uint32_t now_ts = rtc_clock.getCurrentTime();
|
||||||
@@ -1660,7 +1653,7 @@ public:
|
|||||||
} else {
|
} else {
|
||||||
snprintf(title, sizeof(title), "%.23s", ch.name);
|
snprintf(title, sizeof(title), "%.23s", ch.name);
|
||||||
}
|
}
|
||||||
display.drawCenteredHeader(title, true, _ctx_menu.active);
|
drawScreenHeader(display, title, -1, 0, true, _ctx_menu.active);
|
||||||
|
|
||||||
int ch_hist_count = _history.histCountForChannel(_sel_channel_idx);
|
int ch_hist_count = _history.histCountForChannel(_sel_channel_idx);
|
||||||
uint32_t now_ts = rtc_clock.getCurrentTime();
|
uint32_t now_ts = rtc_clock.getCurrentTime();
|
||||||
@@ -1840,15 +1833,15 @@ public:
|
|||||||
char nick_raw[32], nick_trans[32];
|
char nick_raw[32], nick_trans[32];
|
||||||
snprintf(nick_raw, sizeof(nick_raw), "%.*s", rlen, _reply_prefix + 2);
|
snprintf(nick_raw, sizeof(nick_raw), "%.*s", rlen, _reply_prefix + 2);
|
||||||
display.translateUTF8ToBlocks(nick_trans, nick_raw, sizeof(nick_trans));
|
display.translateUTF8ToBlocks(nick_trans, nick_raw, sizeof(nick_trans));
|
||||||
snprintf(title, sizeof(title), "RE:%s", nick_trans);
|
snprintf(title, sizeof(title), "Re: %s", nick_trans);
|
||||||
} else if (_sending_to_channel) {
|
} else if (_sending_to_channel) {
|
||||||
ChannelDetails ch;
|
ChannelDetails ch;
|
||||||
the_mesh.getChannel(_sel_channel_idx, ch);
|
the_mesh.getChannel(_sel_channel_idx, ch);
|
||||||
snprintf(title, sizeof(title), "%.23s", ch.name);
|
snprintf(title, sizeof(title), "%.23s", ch.name);
|
||||||
} else {
|
} else {
|
||||||
snprintf(title, sizeof(title), "TO:%.14s", _sel_contact.name);
|
snprintf(title, sizeof(title), "To: %.14s", _sel_contact.name);
|
||||||
}
|
}
|
||||||
display.drawCenteredHeader(title);
|
drawScreenHeader(display, title);
|
||||||
|
|
||||||
int total_msg_items = 1 + _active_msg_count;
|
int total_msg_items = 1 + _active_msg_count;
|
||||||
drawList(display, total_msg_items, _msg_sel, _msg_scroll, [&](int idx, int y, bool sel, int reserve) {
|
drawList(display, total_msg_items, _msg_sel, _msg_scroll, [&](int idx, int y, bool sel, int reserve) {
|
||||||
|
|||||||
@@ -616,14 +616,14 @@ public:
|
|||||||
const char* flt = (_filter != F_ALL) ? filterLabel(_filter) : nullptr;
|
const char* flt = (_filter != F_ALL) ? filterLabel(_filter) : nullptr;
|
||||||
if (_source == SRC_SCAN) {
|
if (_source == SRC_SCAN) {
|
||||||
char title[28];
|
char title[28];
|
||||||
const char* base = _scanning ? "SCANNING" : "SCAN";
|
const char* base = _scanning ? "Scanning" : "Scan";
|
||||||
if (flt) {
|
if (flt) {
|
||||||
if (!_scanning && _count == 0) snprintf(title, sizeof(title), "SCAN %s: none", flt);
|
if (!_scanning && _count == 0) snprintf(title, sizeof(title), "Scan %s: none", flt);
|
||||||
else snprintf(title, sizeof(title), "%s %s (%d)", base, flt, _count);
|
else snprintf(title, sizeof(title), "%s %s (%d)", base, flt, _count);
|
||||||
} else if (_scanning) snprintf(title, sizeof(title), "SCANNING (%d)", _count);
|
} else if (_scanning) snprintf(title, sizeof(title), "Scanning (%d)", _count);
|
||||||
else if (_count == 0) snprintf(title, sizeof(title), "SCAN: none");
|
else if (_count == 0) snprintf(title, sizeof(title), "Scan: none");
|
||||||
else snprintf(title, sizeof(title), "SCAN (%d)", _count);
|
else snprintf(title, sizeof(title), "Scan (%d)", _count);
|
||||||
display.drawCenteredHeader(title, true, ctxMenuOpen());
|
drawScreenHeader(display, title, -1, 0, true, ctxMenuOpen());
|
||||||
} else {
|
} else {
|
||||||
// Stored list: the filter is a visible tab strip (LEFT/RIGHT switches tabs).
|
// Stored list: the filter is a visible tab strip (LEFT/RIGHT switches tabs).
|
||||||
drawFilterTabs(display);
|
drawFilterTabs(display);
|
||||||
@@ -633,7 +633,7 @@ public:
|
|||||||
char empty[24];
|
char empty[24];
|
||||||
if (_source == SRC_SCAN) {
|
if (_source == SRC_SCAN) {
|
||||||
const char* msg;
|
const char* msg;
|
||||||
if (_scanning) { // the header already says SCANNING: just show it's listening
|
if (_scanning) { // the header already says Scanning: just show it's listening
|
||||||
display.drawTextCentered(display.width() / 2, display.height() / 2 - display.lineStep() / 2, "No replies yet");
|
display.drawTextCentered(display.width() / 2, display.height() / 2 - display.lineStep() / 2, "No replies yet");
|
||||||
int r = drawLoadingDots(display, display.width() / 2, display.height() / 2 + display.lineStep() + 2);
|
int r = drawLoadingDots(display, display.width() / 2, display.height() / 2 + display.lineStep() + 2);
|
||||||
if (mq_delay <= 0 || r < mq_delay) mq_delay = r;
|
if (mq_delay <= 0 || r < mq_delay) mq_delay = r;
|
||||||
@@ -645,8 +645,8 @@ public:
|
|||||||
} else {
|
} else {
|
||||||
const char* hint;
|
const char* hint;
|
||||||
if (_pick_admin_target && !flt) { snprintf(empty, sizeof(empty), "Nothing to admin"); hint = "Repeaters & rooms"; }
|
if (_pick_admin_target && !flt) { snprintf(empty, sizeof(empty), "Nothing to admin"); hint = "Repeaters & rooms"; }
|
||||||
else if (flt) { snprintf(empty, sizeof(empty), "No %s contacts", flt); hint = "[<>] change filter"; }
|
else if (flt) { snprintf(empty, sizeof(empty), "No %s contacts", flt); hint = "Left/Right: filter"; }
|
||||||
else { snprintf(empty, sizeof(empty), "No contacts found"); hint = "[Enter]=Discover"; }
|
else { snprintf(empty, sizeof(empty), "No contacts found"); hint = "Enter: discover";}
|
||||||
display.drawTextCentered(display.width() / 2, display.height() / 2 - display.lineStep() / 2, empty);
|
display.drawTextCentered(display.width() / 2, display.height() / 2 - display.lineStep() / 2, empty);
|
||||||
display.drawTextCentered(display.width() / 2, display.height() / 2 + display.lineStep() / 2, hint);
|
display.drawTextCentered(display.width() / 2, display.height() / 2 + display.lineStep() / 2, hint);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ public:
|
|||||||
buildItems(p);
|
buildItems(p);
|
||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.drawCenteredHeader("REPEATER");
|
drawScreenHeader(display, "Repeater");
|
||||||
|
|
||||||
// Config only — live forwarding stats live on Status › Mesh.
|
// Config only — live forwarding stats live on Status › Mesh.
|
||||||
drawList(display, _item_count, _sel, _scroll, [&](int row, int y, bool sel, int reserve) {
|
drawList(display, _item_count, _sel, _scroll, [&](int row, int y, bool sel, int reserve) {
|
||||||
|
|||||||
@@ -591,7 +591,7 @@ class SettingsScreen : public UIScreen {
|
|||||||
|
|
||||||
int renderScopeMgmt(DisplayDriver& display) {
|
int renderScopeMgmt(DisplayDriver& display) {
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.drawCenteredHeader("SCOPE", true, _scope_action_menu.active);
|
drawScreenHeader(display, "Scope", -1, 0, true, _scope_action_menu.active);
|
||||||
const ScopeList& sl = the_mesh.scopeList();
|
const ScopeList& sl = the_mesh.scopeList();
|
||||||
int total = sl.totalCount() + 1; // +1 synthetic "+ Add scope" row
|
int total = sl.totalCount() + 1; // +1 synthetic "+ Add scope" row
|
||||||
drawList(display, total, _scope_mgmt_sel, _scope_mgmt_scroll, [&](int idx, int y, bool sel, int reserve) {
|
drawList(display, total, _scope_mgmt_sel, _scope_mgmt_scroll, [&](int idx, int y, bool sel, int reserve) {
|
||||||
@@ -651,18 +651,17 @@ public:
|
|||||||
|
|
||||||
if (_scope_mgmt_active) return renderScopeMgmt(display);
|
if (_scope_mgmt_active) return renderScopeMgmt(display);
|
||||||
|
|
||||||
display.drawCenteredHeader("SETTINGS");
|
drawScreenHeader(display, "Settings");
|
||||||
|
|
||||||
int mq_delay = 0;
|
int mq_delay = 0;
|
||||||
_acc.render(display,
|
_acc.render(display,
|
||||||
// Section header: "[+/-] Name"
|
// Section header: disclosure mark + name
|
||||||
[&](int sec, int y, bool sel, int reserve, bool collapsed) {
|
[&](int sec, int y, bool sel, int reserve, bool collapsed) {
|
||||||
_reserve = reserve;
|
_reserve = reserve;
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
drawRowSelection(display, y, sel, reserve);
|
drawRowSelection(display, y, sel, reserve);
|
||||||
display.setCursor(2, y);
|
drawDisclosure(display, 2, y, !collapsed);
|
||||||
display.print(collapsed ? "+" : "-");
|
display.setCursor(2 + 2 * display.getCharWidth(), y);
|
||||||
display.print(" ");
|
|
||||||
display.print(sectionName(_sec_header[sec]));
|
display.print(sectionName(_sec_header[sec]));
|
||||||
},
|
},
|
||||||
// Item row
|
// Item row
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class ToolsScreen : public UIScreen {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
struct Tool { const char* label; const MiniIcon* icon; Action action; };
|
struct Tool { const char* label; const MiniIcon* icon; Action action; };
|
||||||
struct Section { const char* name; const MiniIcon* icon; const Tool* tools; uint8_t count; };
|
struct Section { const char* name; const Tool* tools; uint8_t count; };
|
||||||
|
|
||||||
static const Tool LOCATION_TOOLS[];
|
static const Tool LOCATION_TOOLS[];
|
||||||
static const Tool COMMS_TOOLS[];
|
static const Tool COMMS_TOOLS[];
|
||||||
@@ -79,20 +79,17 @@ public:
|
|||||||
int render(DisplayDriver& display) override {
|
int render(DisplayDriver& display) override {
|
||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.drawCenteredHeader("TOOLS");
|
drawScreenHeader(display, "Tools");
|
||||||
|
|
||||||
const int cw = display.getCharWidth();
|
const int cw = display.getCharWidth();
|
||||||
const int g = gutter(display);
|
const int g = gutter(display);
|
||||||
|
|
||||||
_acc.render(display,
|
_acc.render(display,
|
||||||
// Section header: "[+/-] <icon> Name"
|
// Section header: disclosure mark + name
|
||||||
[&](int sec, int y, bool sel, int reserve, bool collapsed) {
|
[&](int sec, int y, bool sel, int reserve, bool collapsed) {
|
||||||
drawRowSelection(display, y, sel, reserve);
|
drawRowSelection(display, y, sel, reserve);
|
||||||
display.setCursor(2, y);
|
drawDisclosure(display, 2, y, !collapsed);
|
||||||
display.print(collapsed ? "+" : "-");
|
display.setCursor(2 + 2 * cw, y); // the mark stands in for an icon, as in Settings
|
||||||
const int icon_x = 2 + cw + 2;
|
|
||||||
drawIcon(display, icon_x, y, SECTIONS[sec].icon);
|
|
||||||
display.setCursor(icon_x + g, y);
|
|
||||||
display.print(SECTIONS[sec].name);
|
display.print(SECTIONS[sec].name);
|
||||||
},
|
},
|
||||||
// Item: indented "<icon> Label"
|
// Item: indented "<icon> Label"
|
||||||
@@ -142,7 +139,7 @@ const ToolsScreen::Tool ToolsScreen::SYSTEM_TOOLS[] = {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
const ToolsScreen::Section ToolsScreen::SECTIONS[] = {
|
const ToolsScreen::Section ToolsScreen::SECTIONS[] = {
|
||||||
{ "Location", &ICON_MAP_CONTACT, LOCATION_TOOLS, (uint8_t)(sizeof(LOCATION_TOOLS)/sizeof(LOCATION_TOOLS[0])) },
|
{ "Location", LOCATION_TOOLS, (uint8_t)(sizeof(LOCATION_TOOLS)/sizeof(LOCATION_TOOLS[0])) },
|
||||||
{ "Comms", &ICON_ADVERT, COMMS_TOOLS, 4 },
|
{ "Comms", COMMS_TOOLS, 4 },
|
||||||
{ "System", &ICON_GEAR, SYSTEM_TOOLS, (uint8_t)(sizeof(SYSTEM_TOOLS)/sizeof(SYSTEM_TOOLS[0])) },
|
{ "System", SYSTEM_TOOLS, (uint8_t)(sizeof(SYSTEM_TOOLS)/sizeof(SYSTEM_TOOLS[0])) },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -124,14 +124,12 @@ public:
|
|||||||
// Waypoint management UI owns the screen while active.
|
// Waypoint management UI owns the screen while active.
|
||||||
if (_wp.active()) return _wp.render(display);
|
if (_wp.active()) return _wp.render(display);
|
||||||
|
|
||||||
// Title carries the view counter so the bottom hint row can be reclaimed
|
// The title bar carries the view dots so the bottom hint row can be
|
||||||
// for content.
|
// reclaimed for content.
|
||||||
const char* base = (_view == V_MAP) ? (_map_grid ? "TRAIL MAP+" : "TRAIL MAP")
|
const char* title = (_view == V_MAP) ? (_map_grid ? "Trail map+" : "Trail map")
|
||||||
: (_view == V_LIST) ? "TRAIL LIST"
|
: (_view == V_LIST) ? "Trail list"
|
||||||
: "TRAIL";
|
: "Trail";
|
||||||
char title[20];
|
drawScreenHeader(display, title, (int)_view, (int)V_COUNT);
|
||||||
snprintf(title, sizeof(title), "%s %d/%d", base, (int)_view + 1, (int)V_COUNT);
|
|
||||||
display.drawCenteredHeader(title);
|
|
||||||
|
|
||||||
if (_view == V_MAP) renderMap(display);
|
if (_view == V_MAP) renderMap(display);
|
||||||
else if (_view == V_LIST) renderList(display);
|
else if (_view == V_LIST) renderList(display);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "PopupMenu.h"
|
#include "PopupMenu.h"
|
||||||
#include "NavView.h"
|
#include "NavView.h"
|
||||||
#include "DigitEditor.h"
|
#include "DigitEditor.h"
|
||||||
|
#include "InfoKit.h"
|
||||||
#include "../ui-core/GpsAverager.h"
|
#include "../ui-core/GpsAverager.h"
|
||||||
#include "../ui-core/TrackBack.h"
|
#include "../ui-core/TrackBack.h"
|
||||||
|
|
||||||
@@ -140,28 +141,33 @@ class WaypointsView {
|
|||||||
|
|
||||||
void renderAddForm(DisplayDriver& display) {
|
void renderAddForm(DisplayDriver& display) {
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.drawCenteredHeader("ADD WAYPOINT");
|
drawScreenHeader(display, "Add waypoint");
|
||||||
const int top = display.listStart();
|
const int top = display.listStart();
|
||||||
const int step = display.lineStep();
|
const int step = display.lineStep();
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
int y = top + i * step;
|
int y = top + i * step;
|
||||||
bool sel = (i == _add_sel);
|
bool sel = (i == _add_sel);
|
||||||
|
if (i == 3) { // the form's button
|
||||||
|
drawButton(display, (display.width() - buttonWidth(display, "Save")) / 2, y, "Save", sel);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
display.drawSelectionRow(0, y - 1, display.width(), step - 1, sel);
|
display.drawSelectionRow(0, y - 1, display.width(), step - 1, sel);
|
||||||
// While editing a magnitude, draw the row prefix + the digit editor so the
|
const char* label = i == 0 ? "Lat" : i == 1 ? "Lon" : "Label";
|
||||||
// place under the cursor is highlighted; otherwise draw the plain value.
|
const char hemi = i == 0 ? (_add_lat_neg ? 'S' : 'N') : (_add_lon_neg ? 'W' : 'E');
|
||||||
|
// While editing a magnitude, draw the hemisphere + the digit editor so
|
||||||
|
// the place under the cursor is highlighted; otherwise the plain value.
|
||||||
if ((i == 0 || i == 1) && sel && _add_editor.active) {
|
if ((i == 0 || i == 1) && sel && _add_editor.active) {
|
||||||
char prefix[10];
|
const int right = display.width() - 2;
|
||||||
if (i == 0) snprintf(prefix, sizeof(prefix), "Lat: %c ", _add_lat_neg ? 'S' : 'N');
|
const int ex = right - display.getTextWidth(i == 0 ? "00.00000" : "000.00000");
|
||||||
else snprintf(prefix, sizeof(prefix), "Lon: %c ", _add_lon_neg ? 'W' : 'E');
|
display.setCursor(2, y); display.print(label);
|
||||||
display.setCursor(2, y); display.print(prefix);
|
char h[3] = { hemi, ' ', 0 };
|
||||||
_add_editor.render(display, 2 + (int)strlen(prefix) * display.getCharWidth(), y);
|
display.setCursor(ex - display.getTextWidth(h), y); display.print(h);
|
||||||
|
_add_editor.render(display, ex, y);
|
||||||
} else {
|
} else {
|
||||||
char row[28];
|
char val[24];
|
||||||
if (i == 0) snprintf(row, sizeof(row), "Lat: %c %.5f", _add_lat_neg ? 'S' : 'N', _add_lat_mag);
|
if (i < 2) snprintf(val, sizeof(val), "%c %.5f", hemi, i == 0 ? _add_lat_mag : _add_lon_mag);
|
||||||
else if (i == 1) snprintf(row, sizeof(row), "Lon: %c %.5f", _add_lon_neg ? 'W' : 'E', _add_lon_mag);
|
else snprintf(val, sizeof(val), "%s", _add_label[0] ? _add_label : "(auto)");
|
||||||
else if (i == 2) snprintf(row, sizeof(row), "Label: %s", _add_label[0] ? _add_label : "(auto)");
|
info::valueRow(display, y, label, val, sel, 0);
|
||||||
else snprintf(row, sizeof(row), "[Save]");
|
|
||||||
display.setCursor(2, y); display.print(row);
|
|
||||||
}
|
}
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
}
|
}
|
||||||
@@ -171,7 +177,7 @@ class WaypointsView {
|
|||||||
// reports remaining time and the running sample count.
|
// reports remaining time and the running sample count.
|
||||||
void renderAvg(DisplayDriver& display) {
|
void renderAvg(DisplayDriver& display) {
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.drawCenteredHeader("AVERAGING GPS");
|
drawScreenHeader(display, "Averaging GPS");
|
||||||
const int top = display.listStart();
|
const int top = display.listStart();
|
||||||
const int step = display.lineStep();
|
const int step = display.lineStep();
|
||||||
char line[28];
|
char line[28];
|
||||||
@@ -188,9 +194,9 @@ class WaypointsView {
|
|||||||
int mq_delay = 0;
|
int mq_delay = 0;
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
char title[24];
|
char title[24];
|
||||||
snprintf(title, sizeof(title), "WAYPOINTS %d/%d",
|
snprintf(title, sizeof(title), "Waypoints %d/%d",
|
||||||
_task->waypoints().count(), WaypointStore::CAPACITY);
|
_task->waypoints().count(), WaypointStore::CAPACITY);
|
||||||
display.drawCenteredHeader(title);
|
drawScreenHeader(display, title);
|
||||||
|
|
||||||
int n = wpListCount();
|
int n = wpListCount();
|
||||||
int total = n + 1; // final row = "+ Add by coords"
|
int total = n + 1; // final row = "+ Add by coords"
|
||||||
|
|||||||
@@ -965,3 +965,68 @@ inline void drawCheckbox(DisplayDriver& d, int x, int y, bool on) {
|
|||||||
d.drawRect(x, y, box, box);
|
d.drawRect(x, y, box, box);
|
||||||
if (on) d.fillRect(x + 2, y + 2, box - 4, box - 4);
|
if (on) d.fillRect(x + 2, y + 2, box - 4, box - 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Page dots for a screen with views, right edge at x_right, centred on cy:
|
||||||
|
// the current page a filled square, the others a point.
|
||||||
|
inline int pageDotsWidth(DisplayDriver& d, int pages) {
|
||||||
|
const int s = miniIconScale(d);
|
||||||
|
return pages > 1 ? (pages - 1) * 5 * s + 3 * s : 0;
|
||||||
|
}
|
||||||
|
inline void drawPageDots(DisplayDriver& d, int x_right, int cy, int page, int pages) {
|
||||||
|
const int s = miniIconScale(d);
|
||||||
|
for (int i = 0; i < pages; i++) {
|
||||||
|
const int cx = x_right - 2 * s - (pages - 1 - i) * 5 * s; // centre of dot i
|
||||||
|
if (i == page) d.fillRect(cx - s, cy - s, 3 * s, 3 * s);
|
||||||
|
else d.fillRect(cx, cy, s, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A screen's title bar: the title centred over the separator line, page
|
||||||
|
// dots at the right when the screen has pages, and the ≡ hint when it has a
|
||||||
|
// Hold-Enter menu (menu_open highlights it). A title too long to centre goes
|
||||||
|
// left and ends in an ellipsis. No icon: the screen was just picked by one.
|
||||||
|
// Leaves the ink LIGHT.
|
||||||
|
inline void drawScreenHeader(DisplayDriver& d, const char* title, int page = -1, int pages = 0,
|
||||||
|
bool menu_hint = false, bool menu_open = false) {
|
||||||
|
const int s = miniIconScale(d);
|
||||||
|
d.setColor(DisplayDriver::LIGHT);
|
||||||
|
const int hint = menu_hint ? d.menuHintWidth() : 0;
|
||||||
|
const int dots = pages > 1 ? pageDotsWidth(d, pages) + 3 * s : 0;
|
||||||
|
const int reserve = hint + dots;
|
||||||
|
char buf[96];
|
||||||
|
d.translateUTF8ToBlocks(buf, (title && title[0]) ? title : "", sizeof(buf));
|
||||||
|
const int tw = d.getTextWidth(buf), avail = d.width() - reserve - 4;
|
||||||
|
if (tw <= avail) {
|
||||||
|
int x = d.width() / 2 - tw / 2; // centred on the screen
|
||||||
|
if (x + tw > d.width() - reserve - 2) x = (d.width() - reserve) / 2 - tw / 2; // or clear of dots / ≡
|
||||||
|
d.setCursor(x, 0);
|
||||||
|
d.print(buf);
|
||||||
|
} else {
|
||||||
|
d.drawTextEllipsized(2, 0, avail > 0 ? avail : 0, buf);
|
||||||
|
}
|
||||||
|
d.fillRect(0, d.headerH() - d.sepH(), d.width(), d.sepH());
|
||||||
|
if (pages > 1) drawPageDots(d, d.width() - hint - s, (d.headerH() - d.sepH()) / 2, page, pages);
|
||||||
|
if (menu_hint) d.drawContextMenuHint(DisplayDriver::LIGHT, menu_open);
|
||||||
|
}
|
||||||
|
|
||||||
|
// An accordion section's disclosure mark, one character wide: a triangle
|
||||||
|
// pointing right while the section is folded, down while it's open.
|
||||||
|
inline void drawDisclosure(DisplayDriver& d, int x, int y, bool open) {
|
||||||
|
const int s = miniIconScale(d), cy = y + d.getLineHeight() / 2;
|
||||||
|
if (open) for (int i = 0; i < 3; i++) d.fillRect(x + i * s, cy - s + i * s, (5 - 2 * i) * s, s);
|
||||||
|
else for (int i = 0; i < 3; i++) d.fillRect(x + s + i * s, cy - 2 * s + i * s, s, (5 - 2 * i) * s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A push button: its label in a soft pill, filled while selected. One line
|
||||||
|
// tall (the labels have no descenders worth padding for). Returns its width.
|
||||||
|
inline int buttonWidth(DisplayDriver& d, const char* label) { return d.getTextWidth(label) + 6; }
|
||||||
|
inline int drawButton(DisplayDriver& d, int x, int y, const char* label, bool sel) {
|
||||||
|
const int w = buttonWidth(d, label), h = d.getLineHeight();
|
||||||
|
d.setColor(DisplayDriver::LIGHT);
|
||||||
|
if (sel) { d.fillSoftRect(x, y, w, h); d.setColor(DisplayDriver::DARK); }
|
||||||
|
else d.drawSoftRect(x, y, w, h);
|
||||||
|
d.setCursor(x + 3, y);
|
||||||
|
d.print(label);
|
||||||
|
d.setColor(DisplayDriver::LIGHT);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user