mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
feat(nav): Add-by-coords becomes a lat/lon/label form
The on-screen keyboard has no comma or minus, so a single "lat,lon" entry was unworkable. Replace it with a WP_ADD form: separate Lat / Lon / Label rows plus Save. Lat/Lon take a magnitude from the keyboard and a hemisphere (N/S, E/W) toggled with LEFT/RIGHT, so negative coordinates need no minus key. Save validates the range, auto-names a blank label, and stores the point via the normal waypoint path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -92,7 +92,7 @@ Cycle views with **LEFT / RIGHT**:
|
||||
| Readout | LEFT/RIGHT or Enter | Summary shows Speed or Pace (in the global unit system) |
|
||||
| Grid | LEFT/RIGHT or Enter | Toggle scale grid on the map |
|
||||
| Mark here | Enter | Drop a waypoint at the current GPS fix (see below) |
|
||||
| Add by coords | Enter | Add a waypoint by typing `lat,lon` — no GPS fix needed |
|
||||
| Add by coords | Enter | Add a waypoint from a lat/lon/label form — no GPS fix needed |
|
||||
| Waypoints | Enter | Open the waypoint list / navigation |
|
||||
| Start / Stop tracking | Enter | Begin or end a recording session |
|
||||
| Save trail | Enter | Write RAM ring to flash (`/trail`) |
|
||||
@@ -110,7 +110,11 @@ A waypoint is a saved spot — your car, camp, a water source — that you can n
|
||||
|
||||
**Dropping a waypoint** — **Hold Enter → Mark here**. This captures the current GPS fix and opens the on-screen keyboard for a short label (up to 11 characters — e.g. `CAR`, `CAMP`, `H2O`). Leaving it blank auto-names it `WP1`, `WP2`, … Marking works whether or not the trail is being recorded; it needs a GPS fix (otherwise it reports *No GPS fix*).
|
||||
|
||||
**Adding by coordinates** — **Hold Enter → Add by coords** lets you create a waypoint without being there: type `lat,lon` in decimal degrees (e.g. `37.42123,-122.08456`), then a label. No GPS fix is required — handy for entering a meeting point or a spot read off a map. Invalid input reports *Bad coordinates*.
|
||||
**Adding by coordinates** — **Hold Enter → Add by coords** lets you create 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.
|
||||
- **Label** — **Enter** to type a name (blank → auto `WP<n>`).
|
||||
- **Save** — validates the range and stores the waypoint. Missing or out-of-range values report a brief error.
|
||||
|
||||
**On the map** — saved waypoints show on the Trail Map view as a hollow diamond with the label's first two characters beside it (enough to tell nearby waypoints apart). Waypoints and your current GPS position are drawn continuously — even with no trail recording in progress — so the Map view doubles as a live "you + your marks" view, not just a recorded-track plot. With **no trail**, the view auto-fits to your waypoints and position. **While a trail exists**, the view frames the recorded route instead, and any waypoint that falls outside it is clamped to the nearest map edge — a distant mark can't blow up the scale and squash the trail.
|
||||
|
||||
|
||||
@@ -23,18 +23,26 @@ class TrailScreen : public UIScreen {
|
||||
bool _map_grid = true; // show scale grid in map view (Enter to toggle)
|
||||
|
||||
// Waypoint sub-screens layered over the trail views. WP_OFF = normal trail.
|
||||
enum WpMode { WP_OFF, WP_LIST, WP_NAV };
|
||||
enum WpMode { WP_OFF, WP_LIST, WP_NAV, WP_ADD };
|
||||
uint8_t _wp_mode = WP_OFF;
|
||||
int _wp_sel = 0;
|
||||
int _wp_scroll = 0;
|
||||
PopupMenu _wp_ctx; // Rename / Delete on a selected waypoint
|
||||
KeyboardWidget _wp_kb; // label entry (mark new / rename)
|
||||
bool _kb_active = false;
|
||||
bool _kb_coords = false; // keyboard is entering "lat,lon" (then asks for a label)
|
||||
int _kb_rename_idx = -1; // -1 = marking new, ≥0 = renaming that index
|
||||
int32_t _mark_lat = 0, _mark_lon = 0; // pending new-mark position
|
||||
uint32_t _mark_ts = 0;
|
||||
|
||||
// WP_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
|
||||
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 WP_ADD field the keyboard edits (-1 = label/rename)
|
||||
|
||||
// Action popup (Hold Enter / KEY_CONTEXT_MENU). Carries both settings rows
|
||||
// (Min dist, Units — cycled with LEFT/RIGHT while focused) and actions
|
||||
// (Start/Stop tracking, Reset). PopupMenu stores label pointers verbatim, so
|
||||
@@ -63,7 +71,7 @@ public:
|
||||
_wp_mode = WP_OFF;
|
||||
_wp_ctx.active = false;
|
||||
_kb_active = false;
|
||||
_kb_coords = false;
|
||||
_kb_field = -1;
|
||||
}
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
@@ -74,6 +82,7 @@ public:
|
||||
if (_kb_active) return _wp_kb.render(display);
|
||||
|
||||
// Waypoint sub-screens replace the trail views.
|
||||
if (_wp_mode == WP_ADD) { renderAddForm(display); return 1000; }
|
||||
if (_wp_mode == WP_NAV) { renderWpNav(display); return 1000; }
|
||||
if (_wp_mode == WP_LIST) {
|
||||
renderWpList(display);
|
||||
@@ -104,10 +113,11 @@ public:
|
||||
if (_kb_active) {
|
||||
auto r = _wp_kb.handleInput(c);
|
||||
if (r == KeyboardWidget::DONE) {
|
||||
if (_kb_coords) commitCoords(_wp_kb.buf); // parse → then ask for a label
|
||||
else { commitLabel(_wp_kb.buf); _kb_active = false; }
|
||||
if (_kb_field >= 0) applyAddField(_wp_kb.buf); // WP_ADD field edit
|
||||
else commitLabel(_wp_kb.buf); // mark / rename label
|
||||
_kb_active = false; _kb_field = -1;
|
||||
} else if (r == KeyboardWidget::CANCELLED) {
|
||||
_kb_active = false; _kb_coords = false;
|
||||
_kb_active = false; _kb_field = -1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -146,6 +156,26 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// WP_ADD form: type lat/lon (magnitude) + hemisphere toggle + label.
|
||||
if (_wp_mode == WP_ADD) {
|
||||
if (c == KEY_CANCEL) { _wp_mode = WP_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_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
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_ENTER) {
|
||||
if (_add_sel == 0) { _kb_field = 0; _wp_kb.begin(_add_lat, 11); _kb_active = true; }
|
||||
else if (_add_sel == 1) { _kb_field = 1; _wp_kb.begin(_add_lon, 11); _kb_active = true; }
|
||||
else if (_add_sel == 2) { _kb_field = 2; _wp_kb.begin(_add_label, WAYPOINT_LABEL_LEN - 1); _kb_active = true; }
|
||||
else { commitAddForm(); }
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Waypoint navigation view — any nav key returns to the list.
|
||||
if (_wp_mode == WP_NAV) {
|
||||
if (c == KEY_CANCEL || c == KEY_LEFT || c == KEY_PREV ||
|
||||
@@ -390,29 +420,68 @@ private:
|
||||
_kb_active = true;
|
||||
}
|
||||
|
||||
// Add a waypoint by typing "lat,lon" (no GPS fix needed). Opens the keyboard
|
||||
// for coordinates; on a valid parse it falls through to the label keyboard.
|
||||
// Add a waypoint by coordinates (no GPS fix needed). Opens the WP_ADD form.
|
||||
void handleAddByCoords() {
|
||||
if (_task->waypoints().full()) { _task->showAlert("Waypoints full", 1000); return; }
|
||||
_kb_coords = true;
|
||||
_kb_rename_idx = -1;
|
||||
_wp_kb.begin("", 30); // "-12.345678,-123.456789" fits in 30
|
||||
_kb_active = true;
|
||||
_add_lat[0] = _add_lon[0] = _add_label[0] = '\0';
|
||||
_add_lat_neg = _add_lon_neg = false;
|
||||
_add_sel = 0;
|
||||
_wp_mode = WP_ADD;
|
||||
}
|
||||
|
||||
// Parse the typed coordinates; on success store the fix and switch the
|
||||
// keyboard to label entry (reusing the Mark-here commit path).
|
||||
void commitCoords(const char* buf) {
|
||||
int32_t lat, lon;
|
||||
if (!geo::parseLatLon(buf, lat, lon)) {
|
||||
_task->showAlert("Bad coordinates", 1200);
|
||||
_kb_active = false; _kb_coords = false;
|
||||
return;
|
||||
// Store the just-typed value into the focused WP_ADD field.
|
||||
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';
|
||||
}
|
||||
|
||||
// Validate the form and save the waypoint.
|
||||
void commitAddForm() {
|
||||
if (!_add_lat[0] || !_add_lon[0]) { _task->showAlert("Lat/Lon needed", 1200); return; }
|
||||
double la = atof(_add_lat), lo = atof(_add_lon);
|
||||
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) {
|
||||
_task->showAlert("Out of range", 1200); return;
|
||||
}
|
||||
if (_task->waypoints().full()) { _task->showAlert("Waypoints full", 1000); return; }
|
||||
int32_t lat = (int32_t)(la * 1e6 + (la < 0 ? -0.5 : 0.5));
|
||||
int32_t lon = (int32_t)(lo * 1e6 + (lo < 0 ? -0.5 : 0.5));
|
||||
char label[WAYPOINT_LABEL_LEN];
|
||||
if (_add_label[0]) { strncpy(label, _add_label, sizeof(label) - 1); label[sizeof(label) - 1] = '\0'; }
|
||||
else snprintf(label, sizeof(label), "WP%d", _task->waypoints().count() + 1);
|
||||
if (_task->waypoints().add(lat, lon, (uint32_t)rtc_clock.getCurrentTime(), label)) {
|
||||
_task->saveWaypoints();
|
||||
_task->showAlert("Waypoint saved", 800);
|
||||
_wp_mode = WP_OFF;
|
||||
} else {
|
||||
_task->showAlert("Waypoints full", 1000);
|
||||
}
|
||||
}
|
||||
|
||||
void renderAddForm(DisplayDriver& display) {
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawTextCentered(display.width() / 2, 0, "ADD WAYPOINT");
|
||||
display.fillRect(0, display.headerH() - 1, display.width(), display.sepH());
|
||||
const int top = display.listStart();
|
||||
const int step = display.lineStep();
|
||||
const int cw = display.getCharWidth();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int y = top + i * step;
|
||||
bool sel = (i == _add_sel);
|
||||
display.drawSelectionRow(0, y - 1, display.width(), step - 1, sel);
|
||||
display.setCursor(0, y); display.print(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(cw + 2, y); display.print(row);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
}
|
||||
_mark_lat = lat; _mark_lon = lon; _mark_ts = (uint32_t)rtc_clock.getCurrentTime();
|
||||
_kb_coords = false;
|
||||
_kb_rename_idx = -1;
|
||||
_wp_kb.begin("", WAYPOINT_LABEL_LEN - 1); // now name it (empty → auto WP<n>)
|
||||
}
|
||||
|
||||
void commitLabel(const char* buf) {
|
||||
|
||||
Reference in New Issue
Block a user