From a2af1a7409c6d08d886c85110de34c1428b6388f Mon Sep 17 00:00:00 2001 From: MarekZegare4 Date: Sun, 21 Jun 2026 23:09:35 +0200 Subject: [PATCH] refactor(ui): share radio params editor between Settings and Repeater MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../ui-new/RadioParamsEditor.h | 59 +++++++++++++++++++ .../companion_radio/ui-new/RepeaterScreen.h | 41 ++++--------- .../companion_radio/ui-new/SettingsScreen.h | 49 ++++++--------- 3 files changed, 89 insertions(+), 60 deletions(-) create mode 100644 examples/companion_radio/ui-new/RadioParamsEditor.h diff --git a/examples/companion_radio/ui-new/RadioParamsEditor.h b/examples/companion_radio/ui-new/RadioParamsEditor.h new file mode 100644 index 00000000..7042f36d --- /dev/null +++ b/examples/companion_radio/ui-new/RadioParamsEditor.h @@ -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; + } +}; diff --git a/examples/companion_radio/ui-new/RepeaterScreen.h b/examples/companion_radio/ui-new/RepeaterScreen.h index ec358d67..3e546634 100644 --- a/examples/companion_radio/ui-new/RepeaterScreen.h +++ b/examples/companion_radio/ui-new/RepeaterScreen.h @@ -19,7 +19,7 @@ #include #include #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; } diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index 4789406a..3c1af0f1 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -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;