From 92643d1721921e251d8eece5c02da1b8d37e1e6c Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Thu, 18 Jun 2026 09:16:57 +0200 Subject: [PATCH] fix(ui): scale trail-map markers for e-ink, add wrap-around nav Trail map dots/diamonds/cross/start markers and the north indicator were drawn as raw unscaled pixels, so they shrank to near-invisible specks on landscape e-ink's larger font scale; they now route through the mini-icon framework (icons.h) like the rest of the UI, and the grid intersection dots scale with it too. The keyboard's preview/grid separator had the same bug (hardcoded 1px) and now uses display.sepH(). Also makes UP/DOWN (and the keyboard's LEFT/RIGHT/UP/DOWN) wrap top<->bottom across every list, menu and form selector, matching the behavior PopupMenu already had. Co-Authored-By: Claude Sonnet 4.6 --- examples/companion_radio/ui-new/BotScreen.h | 4 +- .../ui-new/DashboardConfigScreen.h | 4 +- .../companion_radio/ui-new/KeyboardWidget.h | 17 +++- .../companion_radio/ui-new/NearbyScreen.h | 4 +- .../companion_radio/ui-new/QuickMsgScreen.h | 16 ++-- .../companion_radio/ui-new/SettingsScreen.h | 22 ++--- examples/companion_radio/ui-new/ToolsScreen.h | 4 +- examples/companion_radio/ui-new/TrailScreen.h | 91 +++++++++++-------- .../companion_radio/ui-new/WaypointsView.h | 8 +- examples/companion_radio/ui-new/icons.h | 44 +++++++++ 10 files changed, 141 insertions(+), 73 deletions(-) diff --git a/examples/companion_radio/ui-new/BotScreen.h b/examples/companion_radio/ui-new/BotScreen.h index 919df405..2b153a90 100644 --- a/examples/companion_radio/ui-new/BotScreen.h +++ b/examples/companion_radio/ui-new/BotScreen.h @@ -146,8 +146,8 @@ public: return true; } // drawList() reclamps _scroll from _sel every render. - if (up && _sel > 0) { _sel--; return true; } - if (down && _sel < ITEM_COUNT - 1) { _sel++; return true; } + if (up) { _sel = (_sel > 0) ? _sel - 1 : ITEM_COUNT - 1; return true; } + if (down) { _sel = (_sel < ITEM_COUNT - 1) ? _sel + 1 : 0; return true; } if (_sel == 0 && (enter || left || right)) { _prefs->bot_enabled ^= 1; diff --git a/examples/companion_radio/ui-new/DashboardConfigScreen.h b/examples/companion_radio/ui-new/DashboardConfigScreen.h index 74b5aae2..a370f05e 100644 --- a/examples/companion_radio/ui-new/DashboardConfigScreen.h +++ b/examples/companion_radio/ui-new/DashboardConfigScreen.h @@ -69,8 +69,8 @@ public: _task->gotoHomeScreen(); return true; } - if (c == KEY_UP && _sel > 0) { _sel--; return true; } - if (c == KEY_DOWN && _sel < FIELD_SLOTS - 1){ _sel++; return true; } + if (c == KEY_UP) { _sel = (_sel > 0) ? _sel - 1 : FIELD_SLOTS - 1; return true; } + if (c == KEY_DOWN) { _sel = (_sel < FIELD_SLOTS - 1) ? _sel + 1 : 0; return true; } if (keyIsPrev(c)) { cycle(_sel, -1); return true; } if (keyIsNext(c)) { cycle(_sel, 1); return true; } if (c == KEY_ENTER) { cycle(_sel, 1); return true; } diff --git a/examples/companion_radio/ui-new/KeyboardWidget.h b/examples/companion_radio/ui-new/KeyboardWidget.h index ad09fe2b..580e6551 100644 --- a/examples/companion_radio/ui-new/KeyboardWidget.h +++ b/examples/companion_radio/ui-new/KeyboardWidget.h @@ -87,10 +87,10 @@ struct KeyboardWidget { const int cell_w = display.width() / KB_COLS_CHAR; // compact: don't stretch cells beyond lh; freed vertical space goes to preview lines const int kb_h = (KB_ROWS_CHAR + 1) * lh; - const int preview_h = display.height() - kb_h - 1; + const int preview_h = display.height() - kb_h - display.sepH(); const int prev_lines = (preview_h / lh) > 1 ? (preview_h / lh) : 1; const int sep_y = prev_lines * lh; - const int chars_y = sep_y + 1; + const int chars_y = sep_y + display.sepH(); const int cell_h = (display.height() - chars_y) / (KB_ROWS_CHAR + 1); const int spec_y = chars_y + KB_ROWS_CHAR * cell_h; const int spec_w = display.width() / KB_SPECIAL; @@ -121,7 +121,7 @@ struct KeyboardWidget { display.setCursor(0, pl * lh); display.print(linebuf_t); } - display.fillRect(0, sep_y, display.width(), 1); + display.fillRect(0, sep_y, display.width(), display.sepH()); // character grid for (int r = 0; r < KB_ROWS_CHAR; r++) { @@ -196,6 +196,9 @@ struct KeyboardWidget { row--; if (row == KB_ROWS_CHAR - 1) // leaving special row upward col = col * KB_COLS_CHAR / KB_SPECIAL; + } else { + row = KB_ROWS_CHAR; // wrap up onto the special row + col = col * KB_SPECIAL / KB_COLS_CHAR; } return NONE; } @@ -204,16 +207,20 @@ struct KeyboardWidget { row++; if (row == KB_ROWS_CHAR) // entering special row col = col * KB_SPECIAL / KB_COLS_CHAR; + } else { + row = 0; // wrap down onto the first char row + col = col * KB_COLS_CHAR / KB_SPECIAL; } return NONE; } if (c == KEY_LEFT) { - if (col > 0) col--; + int max_col = (row == KB_ROWS_CHAR) ? KB_SPECIAL - 1 : KB_COLS_CHAR - 1; + col = (col > 0) ? col - 1 : max_col; return NONE; } if (c == KEY_RIGHT) { int max_col = (row == KB_ROWS_CHAR) ? KB_SPECIAL - 1 : KB_COLS_CHAR - 1; - if (col < max_col) col++; + col = (col < max_col) ? col + 1 : 0; return NONE; } if (c == KEY_ENTER) { diff --git a/examples/companion_radio/ui-new/NearbyScreen.h b/examples/companion_radio/ui-new/NearbyScreen.h index 961a84d4..c6a0e155 100644 --- a/examples/companion_radio/ui-new/NearbyScreen.h +++ b/examples/companion_radio/ui-new/NearbyScreen.h @@ -668,8 +668,8 @@ public: } if (c == KEY_CONTEXT_MENU) { openActionMenu(); return true; } // drawList() reclamps _scroll from _sel every render. - if (c == KEY_UP && _sel > 0) { _sel--; return true; } - if (c == KEY_DOWN && _sel < _count - 1) { _sel++; return true; } + if (c == KEY_UP && _count > 0) { _sel = (_sel > 0) ? _sel - 1 : _count - 1; return true; } + if (c == KEY_DOWN && _count > 0) { _sel = (_sel < _count - 1) ? _sel + 1 : 0; return true; } if (c == KEY_ENTER) { if (_count == 0) { if (_source == SRC_STORED) enterScan(); return true; } _detail = true; diff --git a/examples/companion_radio/ui-new/QuickMsgScreen.h b/examples/companion_radio/ui-new/QuickMsgScreen.h index ba663c56..79c62bd6 100644 --- a/examples/companion_radio/ui-new/QuickMsgScreen.h +++ b/examples/companion_radio/ui-new/QuickMsgScreen.h @@ -1333,8 +1333,8 @@ public: return true; } if (c == KEY_CANCEL) { _task->gotoHomeScreen(); return true; } - if (c == KEY_UP && _mode_sel > 0) { _mode_sel--; return true; } - if (c == KEY_DOWN && _mode_sel < 2) { _mode_sel++; return true; } + if (c == KEY_UP) { _mode_sel = (_mode_sel > 0) ? _mode_sel - 1 : 2; return true; } + if (c == KEY_DOWN) { _mode_sel = (_mode_sel < 2) ? _mode_sel + 1 : 0; return true; } if (c == KEY_CONTEXT_MENU) { // PopupMenu stores the title pointer verbatim — use static strings. static const char* MODE_TITLES[] = { "DM options", "Channel options", "Room options" }; @@ -1455,8 +1455,8 @@ public: } if (c == KEY_CANCEL) { _room_mode = false; _phase = MODE_SELECT; return true; } // drawList() reclamps _contact_scroll from _contact_sel every render. - if (c == KEY_UP && _contact_sel > 0) { _contact_sel--; return true; } - if (c == KEY_DOWN && _contact_sel < _num_contacts - 1) { _contact_sel++; return true; } + if (c == KEY_UP && _num_contacts > 0) { _contact_sel = (_contact_sel > 0) ? _contact_sel - 1 : _num_contacts - 1; return true; } + if (c == KEY_DOWN && _num_contacts > 0) { _contact_sel = (_contact_sel < _num_contacts - 1) ? _contact_sel + 1 : 0; return true; } if (c == KEY_ENTER && _num_contacts > 0) { if (the_mesh.getContactByIdx(_sorted[_contact_sel], _sel_contact)) { _task->clearDMUnread(_sel_contact.id.pub_key); @@ -1545,8 +1545,8 @@ public: } if (c == KEY_CANCEL) { _phase = MODE_SELECT; return true; } // drawList() reclamps _channel_scroll from _channel_sel every render. - if (c == KEY_UP && _channel_sel > 0) { _channel_sel--; return true; } - if (c == KEY_DOWN && _channel_sel < _num_channels - 1) { _channel_sel++; return true; } + if (c == KEY_UP && _num_channels > 0) { _channel_sel = (_channel_sel > 0) ? _channel_sel - 1 : _num_channels - 1; return true; } + if (c == KEY_DOWN && _num_channels > 0) { _channel_sel = (_channel_sel < _num_channels - 1) ? _channel_sel + 1 : 0; return true; } if (c == KEY_ENTER && _num_channels > 0) { _sel_channel_idx = _channel_indices[_channel_sel]; int hc = histCountForChannel(_sel_channel_idx); @@ -1770,8 +1770,8 @@ public: return true; } // drawList() reclamps _msg_scroll from _msg_sel every render. - if (c == KEY_UP && _msg_sel > 0) { _msg_sel--; return true; } - if (c == KEY_DOWN && _msg_sel < total_msg_items - 1) { _msg_sel++; return true; } + if (c == KEY_UP) { _msg_sel = (_msg_sel > 0) ? _msg_sel - 1 : total_msg_items - 1; return true; } + if (c == KEY_DOWN) { _msg_sel = (_msg_sel < total_msg_items - 1) ? _msg_sel + 1 : 0; return true; } if (c == KEY_ENTER) { if (_msg_sel == 0) { _kb->begin(_reply_mode ? _reply_prefix : ""); diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index 7330831c..1c7217e2 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -652,22 +652,20 @@ public: return true; } - if (c == KEY_UP) { + if (c == KEY_UP && _vis_count > 0) { int vi = visIndexOf(_selected); - if (vi > 0) { - vi--; - _selected = _vis[vi]; - if (vi < _scroll) _scroll = vi; - } + vi = (vi > 0) ? vi - 1 : _vis_count - 1; + _selected = _vis[vi]; + if (vi < _scroll) _scroll = vi; + else if (vi >= _scroll + _visible) _scroll = vi - _visible + 1; return true; } - if (c == KEY_DOWN) { + if (c == KEY_DOWN && _vis_count > 0) { int vi = visIndexOf(_selected); - if (vi + 1 < _vis_count) { - vi++; - _selected = _vis[vi]; - if (vi >= _scroll + _visible) _scroll = vi - _visible + 1; - } + vi = (vi + 1 < _vis_count) ? vi + 1 : 0; + _selected = _vis[vi]; + if (vi >= _scroll + _visible) _scroll = vi - _visible + 1; + else if (vi < _scroll) _scroll = vi; return true; } if (c == KEY_CANCEL) { diff --git a/examples/companion_radio/ui-new/ToolsScreen.h b/examples/companion_radio/ui-new/ToolsScreen.h index 7479810a..09bc7d5c 100644 --- a/examples/companion_radio/ui-new/ToolsScreen.h +++ b/examples/companion_radio/ui-new/ToolsScreen.h @@ -27,8 +27,8 @@ public: } bool handleInput(char c) override { - if (c == KEY_UP && _sel > 0) { _sel--; return true; } - if (c == KEY_DOWN && _sel < ITEM_COUNT - 1) { _sel++; return true; } + if (c == KEY_UP) { _sel = (_sel > 0) ? _sel - 1 : ITEM_COUNT - 1; return true; } + if (c == KEY_DOWN) { _sel = (_sel < ITEM_COUNT - 1) ? _sel + 1 : 0; return true; } if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU) { _task->gotoHomeScreen(); return true; } if (c == KEY_ENTER) { if (_sel == 0) { _task->gotoRingtoneEditor(); return true; } diff --git a/examples/companion_radio/ui-new/TrailScreen.h b/examples/companion_radio/ui-new/TrailScreen.h index 5330f19b..3ef4e7f5 100644 --- a/examples/companion_radio/ui-new/TrailScreen.h +++ b/examples/companion_radio/ui-new/TrailScreen.h @@ -8,6 +8,7 @@ #include "../Trail.h" #include "../GeoUtils.h" #include "GfxUtils.h" +#include "icons.h" // scalable mini-icons (map markers, north arrow, grid dots) #include "NavView.h" #include "WaypointsView.h" #include @@ -20,6 +21,11 @@ class TrailScreen : public UIScreen { uint8_t _view = V_SUMMARY; int _summary_scroll = 0; int _list_scroll = 0; + // Top-of-scroll bound for each view, recomputed every render() from the + // live display height (mirrors how _scroll itself is reclamped) — lets + // handleInput() wrap top<->bottom without duplicating the layout math. + int _summary_max_scroll = 0; + int _list_max_scroll = 0; bool _cfg_dirty = false; bool _map_grid = true; // show scale grid in map view (Enter to toggle) @@ -139,10 +145,22 @@ public: if (c == KEY_LEFT || c == KEY_PREV) { _view = (uint8_t)((_view + V_COUNT - 1) % V_COUNT); return true; } if (c == KEY_RIGHT || c == KEY_NEXT) { _view = (uint8_t)((_view + 1) % V_COUNT); return true; } - if (_view == V_SUMMARY && c == KEY_UP && _summary_scroll > 0) { _summary_scroll--; return true; } - if (_view == V_SUMMARY && c == KEY_DOWN) { _summary_scroll++; return true; } - if (_view == V_LIST && c == KEY_UP && _list_scroll > 0) { _list_scroll--; return true; } - if (_view == V_LIST && c == KEY_DOWN) { _list_scroll++; return true; } + if (_view == V_SUMMARY && c == KEY_UP) { + _summary_scroll = (_summary_scroll > 0) ? _summary_scroll - 1 : _summary_max_scroll; + return true; + } + if (_view == V_SUMMARY && c == KEY_DOWN) { + _summary_scroll = (_summary_scroll < _summary_max_scroll) ? _summary_scroll + 1 : 0; + return true; + } + if (_view == V_LIST && c == KEY_UP) { + _list_scroll = (_list_scroll > 0) ? _list_scroll - 1 : _list_max_scroll; + return true; + } + if (_view == V_LIST && c == KEY_DOWN) { + _list_scroll = (_list_scroll < _list_max_scroll) ? _list_scroll + 1 : 0; + return true; + } if (c == KEY_ENTER) { // Short Enter intentionally does nothing destructive — both start and // stop go through the Hold-Enter popup so a stray tap can't change the @@ -401,6 +419,7 @@ private: int max_scroll = SUMMARY_ITEM_COUNT - visible; if (_summary_scroll > max_scroll) _summary_scroll = max_scroll; if (_summary_scroll < 0) _summary_scroll = 0; + _summary_max_scroll = max_scroll; for (int i = 0; i < visible; i++) { int idx = _summary_scroll + i; @@ -421,6 +440,7 @@ private: if (_store->empty()) { display.drawTextCentered(display.width() / 2, top + avail / 2, "No trail yet"); + _list_max_scroll = 0; return; } @@ -432,6 +452,7 @@ private: int max_scroll = total - visible; if (_list_scroll > max_scroll) _list_scroll = max_scroll; if (_list_scroll < 0) _list_scroll = 0; + _list_max_scroll = max_scroll; NodePrefs* p = _task->getNodePrefs(); int32_t tz_off = (int32_t)(p ? p->tz_offset_hours : 0) * 3600; @@ -604,7 +625,7 @@ private: if (have_trail) { int x0, y0; proj.project(_store->at(0).lat_1e6, _store->at(0).lon_1e6, x0, y0); - display.fillRect(x0, y0, 1, 1); + miniIconDrawCentered(display, x0, y0, ICON_MAP_DOT); for (int i = 1; i < _store->count(); i++) { const TrailPoint& pt = _store->at(i); int x1, y1; @@ -625,7 +646,19 @@ private: drawMarkers(display, proj, have_gps, my_lat, my_lon, have_trail); - drawNorthArrow(display, area_x + area_w - 4, area_y + display.getLineHeight() + 1); + int nx, ny; + northIconPos(display, area_x, area_w, area_y, nx, ny); + miniIconDrawTop(display, nx, ny, ICON_MAP_NORTH); + } + + // Top-left placement of the north marker — flush to the top-right corner of + // the map area with a small buffer. Shared with renderGrid so its exclusion + // box always matches exactly where the icon is actually drawn. + static void northIconPos(DisplayDriver& d, int area_x, int area_w, int area_y, + int& nx, int& ny) { + const int s = miniIconScale(d); + nx = area_x + area_w - ICON_MAP_NORTH.w * s - 1; + ny = area_y + 1; } void renderGrid(DisplayDriver& display, const MapProjection& proj) { @@ -699,17 +732,24 @@ private: int x_max = area_x + area_w - 1, y_max = area_y + area_h - 1; // Exclusion boxes so grid dots don't smear the scale label (bottom-left) or - // the north arrow (top-right, mirrors drawNorthArrow). + // the north marker (top-right) — northIconPos() is the single source of + // truth for where that icon actually lands, so this box can't drift out of + // sync with it as the mini-icon scale changes. + const int s = miniIconScale(display); int lbl_x2 = area_x + (int)strlen(lbl) * cw + 1; int lbl_y1 = area_y + area_h - lh - 1; - int arr_x1 = area_x + area_w - 4 - cw - 1; - int arr_y2 = area_y + lh + 1 + 7; + int nx, ny; + northIconPos(display, area_x, area_w, area_y, nx, ny); + int arr_x1 = nx - 1; + int arr_y2 = ny + ICON_MAP_NORTH.h * s + 1; + // Grid dots are drawn at mini-icon scale too, so they stay visible instead + // of shrinking to a single pixel on large-font (e-ink) layouts. auto fillSafe = [&](int x, int y) { if (x < area_x || x > x_max || y < area_y || y > y_max) return; if (x <= lbl_x2 && y >= lbl_y1) return; // under the scale label if (x >= arr_x1 && y <= arr_y2) return; // under the north arrow - display.fillRect(x, y, 1, 1); + display.fillRect(x - s / 2, y - s / 2, s, s); }; // A single dot per intersection — cleaner and more legible on an OLED than @@ -748,41 +788,20 @@ private: } static void drawFilledDot(DisplayDriver& d, int cx, int cy) { - d.fillRect(cx - 1, cy - 1, 3, 3); + miniIconDrawCentered(d, cx, cy, ICON_MAP_DOT); } // Hollow diamond — distinct from the trail's dots (○●), start (+) and // current (✕) markers. static void drawWaypointMarker(DisplayDriver& d, int cx, int cy) { - d.fillRect(cx, cy - 2, 1, 1); - d.fillRect(cx - 1, cy - 1, 1, 1); d.fillRect(cx + 1, cy - 1, 1, 1); - d.fillRect(cx - 2, cy, 1, 1); d.fillRect(cx + 2, cy, 1, 1); - d.fillRect(cx - 1, cy + 1, 1, 1); d.fillRect(cx + 1, cy + 1, 1, 1); - d.fillRect(cx, cy + 2, 1, 1); + miniIconDrawCentered(d, cx, cy, ICON_MAP_WAYPOINT); } static void drawOpenDot(DisplayDriver& d, int cx, int cy) { - d.fillRect(cx - 1, cy - 1, 3, 1); - d.fillRect(cx - 1, cy + 1, 3, 1); - d.fillRect(cx - 1, cy, 1, 1); - d.fillRect(cx + 1, cy, 1, 1); + miniIconDrawCentered(d, cx, cy, ICON_MAP_RING); } static void drawStartMarker(DisplayDriver& d, int cx, int cy) { - d.fillRect(cx - 2, cy, 5, 1); - d.fillRect(cx, cy - 2, 1, 5); + miniIconDrawCentered(d, cx, cy, ICON_MAP_START); } static void drawCurrentMarker(DisplayDriver& d, int cx, int cy) { - for (int i = -2; i <= 2; i++) { - d.fillRect(cx + i, cy + i, 1, 1); - d.fillRect(cx + i, cy - i, 1, 1); - } - } - static void drawNorthArrow(DisplayDriver& d, int cx, int cy) { - int lh = d.getLineHeight(); - int cw = d.getCharWidth(); - d.setCursor(cx - cw / 2, cy - lh); - d.print("N"); - d.fillRect(cx, cy, 1, 1); - d.fillRect(cx - 1, cy + 1, 3, 1); - d.fillRect(cx - 2, cy + 2, 5, 1); - d.fillRect(cx, cy + 3, 1, 4); + miniIconDrawCentered(d, cx, cy, ICON_MAP_CURRENT); } }; diff --git a/examples/companion_radio/ui-new/WaypointsView.h b/examples/companion_radio/ui-new/WaypointsView.h index c816ae02..763a6fad 100644 --- a/examples/companion_radio/ui-new/WaypointsView.h +++ b/examples/companion_radio/ui-new/WaypointsView.h @@ -291,8 +291,8 @@ public: // ADD form: type lat/lon (magnitude) + hemisphere toggle + label. if (_mode == ADD) { if (c == KEY_CANCEL) { _mode = OFF; return true; } - if (c == KEY_UP && _add_sel > 0) { _add_sel--; return true; } - if (c == KEY_DOWN && _add_sel < 3) { _add_sel++; return true; } + if (c == KEY_UP) { _add_sel = (_add_sel > 0) ? _add_sel - 1 : 3; return true; } + if (c == KEY_DOWN) { _add_sel = (_add_sel < 3) ? _add_sel + 1 : 0; return true; } if (c == KEY_LEFT || c == KEY_RIGHT) { if (_add_sel == 0) _add_lat_neg = !_add_lat_neg; // N <-> S else if (_add_sel == 1) _add_lon_neg = !_add_lon_neg; // E <-> W @@ -320,8 +320,8 @@ public: int total = n + 1; // + the "Add by coords" row if (c == KEY_CANCEL) { _mode = OFF; return true; } // drawList() reclamps _scroll from _sel every render. - if (c == KEY_UP && _sel > 0) { _sel--; return true; } - if (c == KEY_DOWN && _sel < total - 1) { _sel++; return true; } + if (c == KEY_UP) { _sel = (_sel > 0) ? _sel - 1 : total - 1; return true; } + if (c == KEY_DOWN) { _sel = (_sel < total - 1) ? _sel + 1 : 0; return true; } if (c == KEY_ENTER) { if (_sel == n) openAddForm(); // last row → open the add form else _mode = NAV; // a waypoint / Trail-start row diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h index 89edbcf4..2983f12c 100644 --- a/examples/companion_radio/ui-new/icons.h +++ b/examples/companion_radio/ui-new/icons.h @@ -96,6 +96,13 @@ inline void miniIconDrawHalo(DisplayDriver& d, int x, int y, const MiniIcon& ic, if (ic.rows[r] & (1 << c)) d.fillRect(x + c * s, y + r * s, s, s); } +// Draw a mini-icon centred on a point (scaled) — used for map markers, which +// are positioned by their centre rather than a text-line top. +inline void miniIconDrawCentered(DisplayDriver& d, int cx, int cy, const MiniIcon& ic) { + const int s = miniIconScale(d); + miniIconDrawTop(d, cx - (ic.w * s) / 2, cy - (ic.h * s) / 2, ic); +} + // Centre a mini-icon inside the slot [x, 0, box_w, box_h] using the current ink // colour (no background). Centres on the box itself, not the text line, so the // glyph sits dead-centre regardless of font line height. @@ -173,6 +180,43 @@ MINI_ICON(ICON_TRAIL, 6, // map pin / location marker (GPS trail logging) packRow(".####."), packRow("..##..")); +// Trail-map markers — centred on a point (see miniIconDrawCentered) rather +// than anchored to a text line. +MINI_ICON(ICON_MAP_DOT, 3, // ● filled trail point + packRow("###"), + packRow("###"), + packRow("###")); +MINI_ICON(ICON_MAP_RING, 3, // ○ hollow ring — marks a new trail segment start + packRow("###"), + packRow("#.#"), + packRow("###")); +MINI_ICON(ICON_MAP_WAYPOINT, 5, // ◇ hollow diamond — saved waypoint + packRow("..#.."), + packRow(".#.#."), + packRow("#...#"), + packRow(".#.#."), + packRow("..#..")); +MINI_ICON(ICON_MAP_START, 5, // + trail start marker + packRow("..#.."), + packRow("..#.."), + packRow("#####"), + packRow("..#.."), + packRow("..#..")); +MINI_ICON(ICON_MAP_CURRENT, 5, // ✕ live position / last trail point + packRow("#...#"), + packRow(".#.#."), + packRow("..#.."), + packRow(".#.#."), + packRow("#...#")); +MINI_ICON(ICON_MAP_NORTH, 5, // "N" with a peaked roof — compass north marker + packRow("..#.."), + packRow(".###."), + packRow("#...#"), + packRow("##..#"), + packRow("#.#.#"), + packRow("#..##"), + packRow("#...#")); + // Keyboard special-key glyphs. MINI_ICON(ICON_SHIFT, 7, // ⇧ caps packRow("...#..."),