From 0fee3bf92c51ff41935dffa7df44bfefd6c0080d Mon Sep 17 00:00:00 2001 From: MarekZegare4 Date: Mon, 22 Jun 2026 13:25:24 +0200 Subject: [PATCH] feat(trail): scroll-editor for add-waypoint coordinates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the text keyboard for the "Add by coords" Lat/Lon fields with the shared digit-by-digit DigitEditor (the radio-frequency widget): Enter opens the editor on the magnitude (LEFT/RIGHT move the cursor, UP/DOWN change the digit), and LEFT/RIGHT on the row still toggles the hemisphere when the editor is closed. The keyboard never fit numeric entry — no minus, easy to type stray letters. Label still uses the keyboard. Docs updated. Verified: WioTrackerL1_companion_solo_dual builds clean. Co-Authored-By: Claude Opus 4.8 --- .../tools_screen/tools_screen.md | 2 +- .../companion_radio/ui-new/WaypointsView.h | 79 +++++++++++-------- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/docs/solo_features/tools_screen/tools_screen.md b/docs/solo_features/tools_screen/tools_screen.md index a38019f1..9dbef8b8 100644 --- a/docs/solo_features/tools_screen/tools_screen.md +++ b/docs/solo_features/tools_screen/tools_screen.md @@ -139,7 +139,7 @@ A waypoint is a saved spot — your car, camp, a water source — that you can n **Adding by coordinates** — open **Hold Enter → Waypoints** and select the **+ Add by coords** row (always the last entry in the list). This creates a waypoint without being there — no GPS fix required (handy for a meeting point or a spot read off a map). It opens a small form with three editable rows plus **Save**: -- **Lat** / **Lon** — **Enter** opens the keyboard to type the value in decimal degrees (magnitude only; the keyboard has no minus sign), and **LEFT/RIGHT** toggles the hemisphere — N/S for latitude, E/W for longitude. +- **Lat** / **Lon** — **Enter** opens the digit-by-digit scroll editor (the same widget as the radio frequency field): **LEFT/RIGHT** move the cursor between decimal places, **UP/DOWN** change the digit under it, **Enter** confirms. With the editor closed, **LEFT/RIGHT** on the row toggles the hemisphere — N/S for latitude, E/W for longitude. - **Label** — **Enter** to type a name (blank → auto `WP`). - **Save** — validates the range and stores the waypoint. Missing or out-of-range values report a brief error. diff --git a/examples/companion_radio/ui-new/WaypointsView.h b/examples/companion_radio/ui-new/WaypointsView.h index 763a6fad..b1b910df 100644 --- a/examples/companion_radio/ui-new/WaypointsView.h +++ b/examples/companion_radio/ui-new/WaypointsView.h @@ -13,6 +13,7 @@ #include "KeyboardWidget.h" #include "PopupMenu.h" #include "NavView.h" +#include "DigitEditor.h" class WaypointsView { UITask* _task; @@ -30,14 +31,18 @@ class WaypointsView { int32_t _mark_lat = 0, _mark_lon = 0; // pending new-mark position uint32_t _mark_ts = 0; - // ADD form — type a waypoint by coordinates. The keyboard has no comma or - // minus, so lat/lon are entered as magnitude + a hemisphere toggle (LEFT/RIGHT). - char _add_lat[12] = "", _add_lon[12] = ""; // magnitude strings + // ADD form — enter a waypoint by coordinates. Lat/Lon are numeric, so they use + // the digit-by-digit scroll editor (same widget as the radio frequency field) + // rather than the text keyboard: Enter opens the editor on the magnitude, + // LEFT/RIGHT on the row toggles the hemisphere (N/S, E/W). Only the Label field + // uses the keyboard. + float _add_lat_mag = 0.0f, _add_lon_mag = 0.0f; // magnitude (degrees) char _add_label[WAYPOINT_LABEL_LEN] = ""; bool _add_lat_neg = false; // true = S bool _add_lon_neg = false; // true = W int _add_sel = 0; // 0=Lat 1=Lon 2=Label 3=Save - int _kb_field = -1; // which ADD field the keyboard edits (-1 = label/rename) + int _kb_field = -1; // ADD label edit (>=0) vs mark/rename (-1) + DigitEditor _add_editor; // active while editing a Lat/Lon magnitude bool useImperial() const { return _task && _task->useImperial(); } bool ownPos(int32_t& lat, int32_t& lon) const { return _task->currentLocation(lat, lon); } @@ -53,38 +58,23 @@ class WaypointsView { // Add a waypoint by coordinates (no GPS fix needed). Opens the ADD form. void openAddForm() { if (_task->waypoints().full()) { _task->showAlert("Waypoints full", 1000); return; } - _add_lat[0] = _add_lon[0] = _add_label[0] = '\0'; + _add_lat_mag = _add_lon_mag = 0.0f; + _add_label[0] = '\0'; _add_lat_neg = _add_lon_neg = false; _add_sel = 0; + _add_editor.active = false; _mode = ADD; } - // Store the just-typed value into the focused ADD field. + // Store the just-typed label (the only ADD field that still uses the keyboard). void applyAddField(const char* buf) { - char* dst = (_kb_field == 0) ? _add_lat - : (_kb_field == 1) ? _add_lon : _add_label; - int cap = (_kb_field == 2) ? WAYPOINT_LABEL_LEN : (int)sizeof(_add_lat); - strncpy(dst, buf, cap - 1); - dst[cap - 1] = '\0'; - } - - // Parse a numeric field: true only when the whole string is a number (so a - // stray letter from the full keyboard can't silently read as 0). - static bool parseNum(const char* s, double& out) { - if (!s || !s[0]) return false; - char* end = nullptr; - out = strtod(s, &end); - if (end == s) return false; // nothing numeric parsed - while (*end == ' ') end++; - return *end == '\0'; // no trailing non-numeric chars + strncpy(_add_label, buf, sizeof(_add_label) - 1); + _add_label[sizeof(_add_label) - 1] = '\0'; } // Validate the ADD form and save the waypoint. void commitAddForm() { - double la, lo; - if (!parseNum(_add_lat, la) || !parseNum(_add_lon, lo)) { - _task->showAlert("Lat/Lon invalid", 1200); return; - } + double la = _add_lat_mag, lo = _add_lon_mag; if (_add_lat_neg) la = -la; if (_add_lon_neg) lo = -lo; if (la < -90.0 || la > 90.0 || lo < -180.0 || lo > 180.0) { @@ -134,12 +124,22 @@ class WaypointsView { int y = top + i * step; bool sel = (i == _add_sel); display.drawSelectionRow(0, y - 1, display.width(), step - 1, sel); - char row[28]; - if (i == 0) snprintf(row, sizeof(row), "Lat: %c %s", _add_lat_neg ? 'S' : 'N', _add_lat[0] ? _add_lat : "--"); - else if (i == 1) snprintf(row, sizeof(row), "Lon: %c %s", _add_lon_neg ? 'W' : 'E', _add_lon[0] ? _add_lon : "--"); - else if (i == 2) snprintf(row, sizeof(row), "Label: %s", _add_label[0] ? _add_label : "(auto)"); - else snprintf(row, sizeof(row), "[Save]"); - display.setCursor(2, y); display.print(row); + // While editing a magnitude, draw the row prefix + the digit editor so the + // place under the cursor is highlighted; otherwise draw the plain value. + if ((i == 0 || i == 1) && sel && _add_editor.active) { + char prefix[10]; + if (i == 0) snprintf(prefix, sizeof(prefix), "Lat: %c ", _add_lat_neg ? 'S' : 'N'); + else snprintf(prefix, sizeof(prefix), "Lon: %c ", _add_lon_neg ? 'W' : 'E'); + display.setCursor(2, y); display.print(prefix); + _add_editor.render(display, 2 + (int)strlen(prefix) * display.getCharWidth(), y); + } else { + char row[28]; + if (i == 0) snprintf(row, sizeof(row), "Lat: %c %.5f", _add_lat_neg ? 'S' : 'N', _add_lat_mag); + else if (i == 1) snprintf(row, sizeof(row), "Lon: %c %.5f", _add_lon_neg ? 'W' : 'E', _add_lon_mag); + else if (i == 2) snprintf(row, sizeof(row), "Label: %s", _add_label[0] ? _add_label : "(auto)"); + else snprintf(row, sizeof(row), "[Save]"); + display.setCursor(2, y); display.print(row); + } display.setColor(DisplayDriver::LIGHT); } } @@ -288,8 +288,17 @@ public: return true; } - // ADD form: type lat/lon (magnitude) + hemisphere toggle + label. + // ADD form: scroll-edit lat/lon (magnitude) + hemisphere toggle + label. if (_mode == ADD) { + // The digit editor, while open, consumes all input (UP/DOWN change the + // digit, LEFT/RIGHT move the cursor, Enter commits, Cancel aborts). + if (_add_editor.active) { + if (_add_editor.handleInput(c) == DigitEditor::DONE) { + if (_add_sel == 0) _add_lat_mag = _add_editor.value; + else _add_lon_mag = _add_editor.value; + } + return true; + } if (c == KEY_CANCEL) { _mode = OFF; 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; } @@ -299,8 +308,8 @@ public: return true; } if (c == KEY_ENTER) { - if (_add_sel == 0) { _kb_field = 0; openKb(_add_lat, 11); } - else if (_add_sel == 1) { _kb_field = 1; openKb(_add_lon, 11); } + if (_add_sel == 0) _add_editor.begin(_add_lat_mag, 0.0f, 90.0f, 2, 5); + else if (_add_sel == 1) _add_editor.begin(_add_lon_mag, 0.0f, 180.0f, 3, 5); else if (_add_sel == 2) { _kb_field = 2; openKb(_add_label, WAYPOINT_LABEL_LEN - 1); } else { commitAddForm(); } return true;