refactor(ui): share radio params editor between Settings and Repeater

The manual Freq/SF/BW/CR editing was duplicated near-verbatim between
Settings › Radio and Tools › Repeater — same digit-by-digit Freq editor,
same SF/BW/CR step bounds, differing only in the target fields and the
apply call. Extract RadioParamsEditor (freq DigitEditor + static SF/BW/CR
steppers), with no dependency on UITask or the radio driver: the screen
passes the freq bounds in, the same way RadioPresetPicker stays decoupled.
Completes the preset-picker consolidation.

Behaviour preserved: a step at the range limit changes nothing and isn't
consumed (falls through), as before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MarekZegare4
2026-06-21 23:09:35 +02:00
parent 4369a0cc74
commit a2af1a7409
3 changed files with 89 additions and 60 deletions

View File

@@ -0,0 +1,59 @@
#pragma once
#include "DigitEditor.h"
#include "../RadioPresets.h"
// Shared manual radio-parameter editing for Settings Radio and Tools
// Repeater: a digit-by-digit Freq editor plus left/right stepping of the
// discrete SF/BW/CR sets. Both screens edit the same field set with the same
// bounds — only the target fields (companion params vs. the dedicated repeater
// profile) and the "apply to the radio" call differ, which stay with the screen.
// No dependency on UITask or the radio driver: the screen passes the freq bounds
// (from radio_driver.getFreqBounds()) in, the same way RadioPresetPicker stays
// decoupled.
struct RadioParamsEditor {
DigitEditor freq; // the modal Freq editor; active flag lives here
float* _freq_field = nullptr; // field begin() is editing, written back on change
bool active() const { return freq.active; }
void render(DisplayDriver& d, int x, int y) { freq.render(d, x, y); }
// Open the digit-by-digit Freq editor on `field`. Bounds are the radio chip's
// own validated range, so a digit can't be nudged to a value setFrequency()
// would reject. 3 integer + 3 decimal digits = kHz precision (the SX126x
// range fits 3 integer digits).
void beginFreq(float& field, float min_mhz, float max_mhz) {
_freq_field = &field;
freq.begin(field, min_mhz, max_mhz, 3, 3);
}
// Feed a key to the open Freq editor and write the edited value back to the
// field. Returns true if the field changed — the caller then applies it to the
// radio and marks itself dirty. Cursor moves / open-close consume the key but
// report no change.
bool handleFreqInput(char c) {
float before = freq.value;
freq.handleInput(c);
if (_freq_field && freq.value != before) { *_freq_field = freq.value; return true; }
return false;
}
// Step the discrete SF/BW/CR sets within their valid LoRa ranges. dir > 0 =
// right/up, dir < 0 = left/down. Mutates and returns true only when the value
// actually changed (so a step at the limit is a no-op the caller can ignore).
static bool stepSF(uint8_t& sf, int dir) {
if (dir > 0 && sf < 12) { sf++; return true; }
if (dir < 0 && sf > 5) { sf--; return true; }
return false;
}
static bool stepCR(uint8_t& cr, int dir) {
if (dir > 0 && cr < 8) { cr++; return true; }
if (dir < 0 && cr > 5) { cr--; return true; }
return false;
}
static bool stepBW(float& bw, int dir) {
int idx = nearestBwIndex(bw);
if (dir > 0 && idx < LORA_BW_OPT_COUNT - 1) { bw = LORA_BW_OPTS[idx + 1]; return true; }
if (dir < 0 && idx > 0) { bw = LORA_BW_OPTS[idx - 1]; return true; }
return false;
}
};

View File

@@ -19,7 +19,7 @@
#include <helpers/ui/DisplayDriver.h>
#include <helpers/ui/UIScreen.h>
#include "icons.h"
#include "DigitEditor.h"
#include "RadioParamsEditor.h"
#include "RadioPresetPicker.h"
#include "../RadioPresets.h"
#include "../MyMesh.h"
@@ -41,7 +41,7 @@ class RepeaterScreen : public UIScreen {
int _item_count;
RadioPresetPicker _picker;
DigitEditor _freq_editor;
RadioParamsEditor _editor;
// The dedicated repeater profile's fields, as the shared preset picker's target
// (Settings Radio points the same picker at the companion's own params).
@@ -130,7 +130,7 @@ public:
void enter() {
_dirty = false; _sel = 0; _scroll = 0;
_picker.menu.active = false; _freq_editor.active = false;
_picker.menu.active = false; _editor.freq.active = false;
_picker.saving = false; _picker.deleting = false;
}
@@ -167,8 +167,8 @@ public:
display.drawSelectionRow(0, y - 1, display.width() - reserve, item_h - 1, sel);
display.setCursor(2, y);
display.print(itemLabel(item));
if (item == IT_RFREQ && sel && _freq_editor.active) {
_freq_editor.render(display, display.valCol(), y);
if (item == IT_RFREQ && sel && _editor.active()) {
_editor.render(display, display.valCol(), y);
} else {
itemValue(item, p, val, sizeof(val));
display.drawTextRightAlign(display.width() - reserve - 2, y, val);
@@ -178,7 +178,7 @@ public:
drawScrollIndicator(display, start_y, visible * item_h, total, visible, _scroll);
display.setColor(DisplayDriver::LIGHT);
if (_picker.menu.active) _picker.menu.render(display);
return (_picker.menu.active || _freq_editor.active) ? 50 : 500;
return (_picker.menu.active || _editor.active()) ? 50 : 500;
}
bool handleInput(char c) override {
@@ -223,14 +223,8 @@ public:
}
return true;
}
if (_freq_editor.active) {
float before = _freq_editor.value;
_freq_editor.handleInput(c);
if (p && _freq_editor.value != before) {
p->repeater_freq = _freq_editor.value;
the_mesh.applyRepeaterRadio();
_dirty = true;
}
if (_editor.active()) {
if (_editor.handleFreqInput(c) && p) { the_mesh.applyRepeaterRadio(); _dirty = true; }
return true;
}
@@ -269,22 +263,13 @@ public:
if (item == IT_RPRESET && enter) { _picker.open(p, rptTarget(p), "Repeater Preset"); return true; }
if (item == IT_RFREQ && enter) {
float lo, hi; radio_driver.getFreqBounds(lo, hi);
_freq_editor.begin(p->repeater_freq, lo, hi, 3, 3);
_editor.beginFreq(p->repeater_freq, lo, hi);
return true;
}
if (item == IT_RSF) {
if (right && p->repeater_sf < 12) { p->repeater_sf++; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
if (left && p->repeater_sf > 5) { p->repeater_sf--; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
}
if (item == IT_RBW) {
int idx = nearestBwIndex(p->repeater_bw);
if (right && idx < LORA_BW_OPT_COUNT - 1) { p->repeater_bw = LORA_BW_OPTS[idx + 1]; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
if (left && idx > 0) { p->repeater_bw = LORA_BW_OPTS[idx - 1]; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
}
if (item == IT_RCR) {
if (right && p->repeater_cr < 8) { p->repeater_cr++; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
if (left && p->repeater_cr > 5) { p->repeater_cr--; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
}
int dir = right ? 1 : (left ? -1 : 0);
if (item == IT_RSF && dir && RadioParamsEditor::stepSF(p->repeater_sf, dir)) { the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
if (item == IT_RBW && dir && RadioParamsEditor::stepBW(p->repeater_bw, dir)) { the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
if (item == IT_RCR && dir && RadioParamsEditor::stepCR(p->repeater_cr, dir)) { the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
if (item == IT_SKIP && (left || right || enter)) {
p->repeat_skip_adverts ^= 1; _dirty = true; return true;
}

View File

@@ -4,7 +4,7 @@
#include "../Features.h"
#include "../RadioPresets.h"
#include "DigitEditor.h"
#include "RadioParamsEditor.h"
#include "RadioPresetPicker.h"
class SettingsScreen : public UIScreen {
@@ -508,8 +508,8 @@ class SettingsScreen : public UIScreen {
} else if (item == CUSTOM_FREQ) {
display.print("Freq");
int xc = valCol(display);
if (sel && _freq_editor.active) {
_freq_editor.render(display, xc, y);
if (sel && _editor.active()) {
_editor.render(display, xc, y);
} else {
char buf[10];
snprintf(buf, sizeof(buf), "%.3f", p ? p->freq : 0.0f);
@@ -650,9 +650,9 @@ class SettingsScreen : public UIScreen {
// _kb is open to name a new preset, not a message slot.
RadioPresetPicker _picker;
// Digit-by-digit Freq editor (see DigitEditor.h) — only this field uses it;
// SF/BW/CR are small discrete sets where a plain left/right cycle is fine.
DigitEditor _freq_editor;
// Manual radio-parameter editing (digit-by-digit Freq editor + SF/BW/CR
// stepping), shared with Tools Repeater — see RadioParamsEditor.h.
RadioParamsEditor _editor;
public:
SettingsScreen(UITask* task, KeyboardWidget* kb)
@@ -667,7 +667,7 @@ public:
_selected = SECTION_DISPLAY;
buildVis();
_scroll = 0;
_freq_editor.active = false;
_editor.freq.active = false;
}
int render(DisplayDriver& display) override {
@@ -715,14 +715,8 @@ public:
}
// Digit-by-digit Freq editor
if (_freq_editor.active) {
float before = _freq_editor.value;
_freq_editor.handleInput(c);
if (p && _freq_editor.value != before) {
p->freq = _freq_editor.value;
_task->applyRadioParams();
_dirty = true;
}
if (_editor.active()) {
if (_editor.handleFreqInput(c) && p) { _task->applyRadioParams(); _dirty = true; }
return true;
}
@@ -861,28 +855,19 @@ public:
_picker.open(p, radioTarget(p), "Radio Preset");
return true;
}
// Enter Freq's digit-by-digit editor (see DigitEditor.h). Bounds come from
// the radio driver itself (RadioLib's own validated range for this chip)
// so a digit can never be nudged to a value setFrequency() would reject.
// Enter Freq's digit-by-digit editor. Bounds come from the radio driver
// itself (RadioLib's own validated range for this chip) so a digit can never
// be nudged to a value setFrequency() would reject.
if (_selected == CUSTOM_FREQ && p && enter) {
float min_mhz, max_mhz;
radio_driver.getFreqBounds(min_mhz, max_mhz);
_freq_editor.begin(p->freq, min_mhz, max_mhz, 3, 3); // SX1262 range fits 3 integer digits; 3 decimals = kHz
_editor.beginFreq(p->freq, min_mhz, max_mhz);
return true;
}
if (_selected == CUSTOM_SF && p) {
if (right && p->sf < 12) { p->sf++; _task->applyRadioParams(); _dirty = true; return true; }
if (left && p->sf > 5) { p->sf--; _task->applyRadioParams(); _dirty = true; return true; }
}
if (_selected == CUSTOM_BW && p) {
int idx = nearestBwIndex(p->bw);
if (right && idx < LORA_BW_OPT_COUNT - 1) { p->bw = LORA_BW_OPTS[idx + 1]; _task->applyRadioParams(); _dirty = true; return true; }
if (left && idx > 0) { p->bw = LORA_BW_OPTS[idx - 1]; _task->applyRadioParams(); _dirty = true; return true; }
}
if (_selected == CUSTOM_CR && p) {
if (right && p->cr < 8) { p->cr++; _task->applyRadioParams(); _dirty = true; return true; }
if (left && p->cr > 5) { p->cr--; _task->applyRadioParams(); _dirty = true; return true; }
}
int dir = right ? 1 : (left ? -1 : 0);
if (_selected == CUSTOM_SF && p && dir && RadioParamsEditor::stepSF(p->sf, dir)) { _task->applyRadioParams(); _dirty = true; return true; }
if (_selected == CUSTOM_BW && p && dir && RadioParamsEditor::stepBW(p->bw, dir)) { _task->applyRadioParams(); _dirty = true; return true; }
if (_selected == CUSTOM_CR && p && dir && RadioParamsEditor::stepCR(p->cr, dir)) { _task->applyRadioParams(); _dirty = true; return true; }
if (_selected == POWER_SAVE && p && (left || right || enter)) {
if (p->client_repeat) { _task->showAlert("Off while repeating", 900); return true; }
p->rx_powersave ^= 1;