mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
refactor(ui): share radio preset picker between Settings and Repeater
Settings › Radio and Tools › Repeater had near-identical preset-picker logic (name/list-index lookup, save/delete, popup build, selection handling) plus their own copy of the nearest-bandwidth search — only the target fields (companion params vs. dedicated repeater profile) and the apply call differed. Extract the shared logic into RadioPresetPicker (a Target of field pointers + a Result the screen acts on), with no dependency on UITask so each screen keeps its own apply/dirty/keyboard/alert handling. Move the nearest-bandwidth search to nearestBwIndex() in RadioPresets.h. Removes ~6 duplicated methods and 4 state fields from each screen. Verified: WioTrackerL1_companion_solo_dual builds clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,18 @@ static const int RADIO_PRESET_COUNT = sizeof(RADIO_PRESETS) / sizeof(RADIO_PRESE
|
|||||||
static const float LORA_BW_OPTS[] = { 7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f, 62.5f, 125.0f, 250.0f, 500.0f };
|
static const float LORA_BW_OPTS[] = { 7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f, 62.5f, 125.0f, 250.0f, 500.0f };
|
||||||
static const int LORA_BW_OPT_COUNT = sizeof(LORA_BW_OPTS) / sizeof(LORA_BW_OPTS[0]);
|
static const int LORA_BW_OPT_COUNT = sizeof(LORA_BW_OPTS) / sizeof(LORA_BW_OPTS[0]);
|
||||||
|
|
||||||
|
// Index of the LORA_BW_OPTS entry nearest `bw` — used both to render the current
|
||||||
|
// BW and to step left/right through the table without drifting off-grid.
|
||||||
|
static inline int nearestBwIndex(float bw) {
|
||||||
|
int best = 0;
|
||||||
|
float best_diff = 1e9f;
|
||||||
|
for (int i = 0; i < LORA_BW_OPT_COUNT; i++) {
|
||||||
|
float diff = fabsf(bw - LORA_BW_OPTS[i]);
|
||||||
|
if (diff < best_diff) { best_diff = diff; best = i; }
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
// True if (freq, bw, sf, cr) matches the radio's currently-applied (cur_*) params.
|
// True if (freq, bw, sf, cr) matches the radio's currently-applied (cur_*) params.
|
||||||
// Floats are compared with a small epsilon since they may have round-tripped
|
// Floats are compared with a small epsilon since they may have round-tripped
|
||||||
// through unit conversions (e.g. kHz integers from the companion app).
|
// through unit conversions (e.g. kHz integers from the companion app).
|
||||||
|
|||||||
162
examples/companion_radio/ui-new/RadioPresetPicker.h
Normal file
162
examples/companion_radio/ui-new/RadioPresetPicker.h
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string.h>
|
||||||
|
#include "PopupMenu.h"
|
||||||
|
#include "../RadioPresets.h"
|
||||||
|
#include "../NodePrefs.h"
|
||||||
|
|
||||||
|
// Shared "radio preset" picker used by both Settings › Radio and Tools ›
|
||||||
|
// Repeater. Both edit the same 4 user preset slots in NodePrefs and build the
|
||||||
|
// same popup layout; they differ only in which freq/bw/sf/cr fields they target
|
||||||
|
// (the companion's own params vs. the dedicated repeater profile) and in what
|
||||||
|
// runs when a value is applied. This struct owns the popup + save/delete state
|
||||||
|
// and the lookup/save logic; the owning screen passes a Target pointing at its
|
||||||
|
// fields and handles apply()/dirty/keyboard/alert on the Result it returns.
|
||||||
|
//
|
||||||
|
// Popup layout (built by open()):
|
||||||
|
// [0] = "+ Save current..."
|
||||||
|
// [1 .. RADIO_PRESET_COUNT] = built-in presets
|
||||||
|
// [.. + user_count] = saved user presets (empty slots skipped)
|
||||||
|
// [last, if user_count > 0] = "- Delete preset..."
|
||||||
|
struct RadioPresetPicker {
|
||||||
|
// The four radio fields this picker reads and writes.
|
||||||
|
struct Target {
|
||||||
|
float* freq;
|
||||||
|
float* bw;
|
||||||
|
uint8_t* sf;
|
||||||
|
uint8_t* cr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// What the owning screen should do after a SELECTED item is handled.
|
||||||
|
enum Result {
|
||||||
|
NONE, // nothing to do (sub-menu opened, or a no-op row)
|
||||||
|
START_SAVE, // open the keyboard to name a new preset, then call save()
|
||||||
|
APPLIED, // target fields changed — apply to the radio + mark dirty
|
||||||
|
DELETED // a saved preset was removed — mark dirty + alert
|
||||||
|
};
|
||||||
|
|
||||||
|
PopupMenu menu;
|
||||||
|
uint8_t user_slot[NodePrefs::USER_RADIO_PRESET_MAX]; // popup row → NodePrefs slot
|
||||||
|
int user_count = 0;
|
||||||
|
bool saving = false; // keyboard is open to name a new preset
|
||||||
|
bool deleting = false; // menu is showing the delete sub-list
|
||||||
|
|
||||||
|
static bool matches(const Target& t, float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||||
|
return radioParamsMatchPreset(*t.freq, *t.bw, *t.sf, *t.cr, freq, bw, sf, cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display name for the target: a built-in preset, a saved user preset, or
|
||||||
|
// "Custom" if the params don't exactly match any of them.
|
||||||
|
const char* currentName(NodePrefs* p, const Target& t) const {
|
||||||
|
if (!p) return "Custom";
|
||||||
|
for (int i = 0; i < RADIO_PRESET_COUNT; i++) {
|
||||||
|
const RadioPreset& r = RADIO_PRESETS[i];
|
||||||
|
if (matches(t, r.freq, r.bw, r.sf, r.cr)) return r.name;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++) {
|
||||||
|
const auto& u = p->user_radio_presets[i];
|
||||||
|
if (u.name[0] && matches(t, u.freq, u.bw, u.sf, u.cr)) return u.name;
|
||||||
|
}
|
||||||
|
return "Custom";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position of the target within the popup list built by open(), or -1
|
||||||
|
// ("Custom") if nothing matches.
|
||||||
|
int currentListIndex(NodePrefs* p, const Target& t) const {
|
||||||
|
if (!p) return -1;
|
||||||
|
for (int i = 0; i < RADIO_PRESET_COUNT; i++) {
|
||||||
|
const RadioPreset& r = RADIO_PRESETS[i];
|
||||||
|
if (matches(t, r.freq, r.bw, r.sf, r.cr)) return i + 1;
|
||||||
|
}
|
||||||
|
int pos = RADIO_PRESET_COUNT + 1;
|
||||||
|
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++) {
|
||||||
|
const auto& u = p->user_radio_presets[i];
|
||||||
|
if (!u.name[0]) continue;
|
||||||
|
if (matches(t, u.freq, u.bw, u.sf, u.cr)) return pos;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the target's params as a named user preset: overwrite a slot with the
|
||||||
|
// same name if one exists, else the first empty slot, else slot 0 (oldest).
|
||||||
|
// Returns true if anything was written (caller alerts + marks dirty).
|
||||||
|
bool save(NodePrefs* p, const char* name, const Target& t) {
|
||||||
|
if (!p || !name || !name[0]) return false;
|
||||||
|
int slot = -1;
|
||||||
|
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++)
|
||||||
|
if (strcmp(p->user_radio_presets[i].name, name) == 0) { slot = i; break; }
|
||||||
|
if (slot < 0)
|
||||||
|
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++)
|
||||||
|
if (!p->user_radio_presets[i].name[0]) { slot = i; break; }
|
||||||
|
if (slot < 0) slot = 0;
|
||||||
|
NodePrefs::UserRadioPreset& u = p->user_radio_presets[slot];
|
||||||
|
strncpy(u.name, name, sizeof(u.name) - 1);
|
||||||
|
u.name[sizeof(u.name) - 1] = '\0';
|
||||||
|
u.freq = *t.freq; u.bw = *t.bw; u.sf = *t.sf; u.cr = *t.cr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void open(NodePrefs* p, const Target& t, const char* title) {
|
||||||
|
deleting = false;
|
||||||
|
menu.begin(title, 6);
|
||||||
|
menu.addItem("+ Save current...");
|
||||||
|
for (int i = 0; i < RADIO_PRESET_COUNT; i++) menu.addItem(RADIO_PRESETS[i].name);
|
||||||
|
user_count = 0;
|
||||||
|
if (p) {
|
||||||
|
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++) {
|
||||||
|
if (!p->user_radio_presets[i].name[0]) continue;
|
||||||
|
menu.addItem(p->user_radio_presets[i].name);
|
||||||
|
user_slot[user_count++] = (uint8_t)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (user_count > 0) menu.addItem("- Delete preset...");
|
||||||
|
int idx = currentListIndex(p, t);
|
||||||
|
menu.setSelected(idx >= 0 ? idx : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reuses `menu` for a second-level list of just the saved user presets (their
|
||||||
|
// slot mapping in user_slot is still the one open() built, since this is only
|
||||||
|
// reached from within that same popup).
|
||||||
|
void openDelete(NodePrefs* p) {
|
||||||
|
menu.begin("Delete Preset", 6);
|
||||||
|
for (int i = 0; i < user_count; i++)
|
||||||
|
menu.addItem(p->user_radio_presets[user_slot[i]].name);
|
||||||
|
deleting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the index the popup reports as SELECTED. Mutates target fields on a
|
||||||
|
// built-in/user pick; deletes a slot in the delete sub-list. See Result.
|
||||||
|
Result onSelected(int idx, NodePrefs* p, const Target& t) {
|
||||||
|
if (!p) return NONE;
|
||||||
|
if (deleting) {
|
||||||
|
Result r = NONE;
|
||||||
|
if (idx >= 0 && idx < user_count) {
|
||||||
|
p->user_radio_presets[user_slot[idx]].name[0] = '\0';
|
||||||
|
r = DELETED;
|
||||||
|
}
|
||||||
|
deleting = false;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
const int builtin_base = 1;
|
||||||
|
const int user_base = builtin_base + RADIO_PRESET_COUNT;
|
||||||
|
const int delete_idx = user_base + user_count;
|
||||||
|
if (idx == 0) {
|
||||||
|
saving = true;
|
||||||
|
return START_SAVE;
|
||||||
|
}
|
||||||
|
if (idx >= builtin_base && idx < user_base) {
|
||||||
|
const RadioPreset& r = RADIO_PRESETS[idx - builtin_base];
|
||||||
|
*t.freq = r.freq; *t.bw = r.bw; *t.sf = r.sf; *t.cr = r.cr;
|
||||||
|
return APPLIED;
|
||||||
|
}
|
||||||
|
if (idx >= user_base && idx < delete_idx) {
|
||||||
|
const NodePrefs::UserRadioPreset& u = p->user_radio_presets[user_slot[idx - user_base]];
|
||||||
|
*t.freq = u.freq; *t.bw = u.bw; *t.sf = u.sf; *t.cr = u.cr;
|
||||||
|
return APPLIED;
|
||||||
|
}
|
||||||
|
if (user_count > 0 && idx == delete_idx) {
|
||||||
|
openDelete(p);
|
||||||
|
}
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// Tools › Repeater — consolidates the repeater toggle, its flood "politeness"
|
// Tools › Repeater — consolidates the repeater toggle, its flood forwarding
|
||||||
// filters, an optional dedicated radio profile, and live forwarding stats on one
|
// filters, and an optional dedicated radio profile on one screen. A dedicated
|
||||||
// screen. A dedicated screen (vs. the old Settings › Radio sub-items) gives
|
// screen (vs. the old Settings › Radio sub-items) gives full-width rows, so the
|
||||||
// full-width rows, so the longer labels no longer collide with the value column,
|
// longer labels no longer collide with the value column. Live forwarding stats
|
||||||
// and keeps the relaying controls next to the numbers that show them working.
|
// live separately on Tools › Diagnostics.
|
||||||
//
|
//
|
||||||
// Network modes:
|
// Network modes:
|
||||||
// - Current — repeat on the companion's current frequency. Opt-in only:
|
// - Current — repeat on the companion's current frequency. Opt-in only:
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
#include <helpers/ui/DisplayDriver.h>
|
#include <helpers/ui/DisplayDriver.h>
|
||||||
#include <helpers/ui/UIScreen.h>
|
#include <helpers/ui/UIScreen.h>
|
||||||
#include "icons.h"
|
#include "icons.h"
|
||||||
#include "PopupMenu.h"
|
|
||||||
#include "DigitEditor.h"
|
#include "DigitEditor.h"
|
||||||
|
#include "RadioPresetPicker.h"
|
||||||
#include "../RadioPresets.h"
|
#include "../RadioPresets.h"
|
||||||
#include "../MyMesh.h"
|
#include "../MyMesh.h"
|
||||||
|
|
||||||
@@ -40,22 +40,13 @@ class RepeaterScreen : public UIScreen {
|
|||||||
uint8_t _items[12];
|
uint8_t _items[12];
|
||||||
int _item_count;
|
int _item_count;
|
||||||
|
|
||||||
PopupMenu _preset_menu;
|
RadioPresetPicker _picker;
|
||||||
DigitEditor _freq_editor;
|
DigitEditor _freq_editor;
|
||||||
uint8_t _preset_user_slot[NodePrefs::USER_RADIO_PRESET_MAX];
|
|
||||||
int _preset_user_count = 0;
|
|
||||||
bool _preset_saving = false; // keyboard is open to name a new preset
|
|
||||||
bool _preset_deleting = false; // _preset_menu is showing the delete sub-list
|
|
||||||
|
|
||||||
// Nearest entry in LORA_BW_OPTS to the repeater profile's bw.
|
// The dedicated repeater profile's fields, as the shared preset picker's target
|
||||||
int rptBwIndex(NodePrefs* p) const {
|
// (Settings › Radio points the same picker at the companion's own params).
|
||||||
int best = 0;
|
RadioPresetPicker::Target rptTarget(NodePrefs* p) const {
|
||||||
float best_diff = 1e9f;
|
return { &p->repeater_freq, &p->repeater_bw, &p->repeater_sf, &p->repeater_cr };
|
||||||
for (int i = 0; i < LORA_BW_OPT_COUNT; i++) {
|
|
||||||
float diff = fabsf(p->repeater_bw - LORA_BW_OPTS[i]);
|
|
||||||
if (diff < best_diff) { best_diff = diff; best = i; }
|
|
||||||
}
|
|
||||||
return best;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void buildItems(NodePrefs* p) {
|
void buildItems(NodePrefs* p) {
|
||||||
@@ -98,69 +89,12 @@ class RepeaterScreen : public UIScreen {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name of the preset matching the repeater profile, or "Custom".
|
|
||||||
const char* rptPresetName(NodePrefs* p) const {
|
|
||||||
for (int i = 0; i < RADIO_PRESET_COUNT; i++) {
|
|
||||||
const RadioPreset& r = RADIO_PRESETS[i];
|
|
||||||
if (radioParamsMatchPreset(p->repeater_freq, p->repeater_bw, p->repeater_sf, p->repeater_cr, r.freq, r.bw, r.sf, r.cr))
|
|
||||||
return r.name;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++) {
|
|
||||||
const NodePrefs::UserRadioPreset& u = p->user_radio_presets[i];
|
|
||||||
if (!u.name[0]) continue;
|
|
||||||
if (radioParamsMatchPreset(p->repeater_freq, p->repeater_bw, p->repeater_sf, p->repeater_cr, u.freq, u.bw, u.sf, u.cr))
|
|
||||||
return u.name;
|
|
||||||
}
|
|
||||||
return "Custom";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position of the repeater profile within the popup list built by
|
|
||||||
// openPresetMenu() below ("Save current..." at 0, then built-ins, then
|
|
||||||
// non-empty user slots in slot order) — or -1 ("Custom") if nothing matches.
|
|
||||||
int currentPresetListIndex(NodePrefs* p) const {
|
|
||||||
for (int i = 0; i < RADIO_PRESET_COUNT; i++) {
|
|
||||||
const RadioPreset& r = RADIO_PRESETS[i];
|
|
||||||
if (radioParamsMatchPreset(p->repeater_freq, p->repeater_bw, p->repeater_sf, p->repeater_cr, r.freq, r.bw, r.sf, r.cr))
|
|
||||||
return i + 1;
|
|
||||||
}
|
|
||||||
int pos = RADIO_PRESET_COUNT + 1;
|
|
||||||
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++) {
|
|
||||||
const auto& u = p->user_radio_presets[i];
|
|
||||||
if (!u.name[0]) continue;
|
|
||||||
if (radioParamsMatchPreset(p->repeater_freq, p->repeater_bw, p->repeater_sf, p->repeater_cr, u.freq, u.bw, u.sf, u.cr))
|
|
||||||
return pos;
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the current repeater profile as a named user preset: overwrite a slot
|
|
||||||
// with the same name if one exists, else the first empty slot, else slot 0.
|
|
||||||
// Shared with Settings' freq/bw/sf/cr — these are the same 4 user slots, just
|
|
||||||
// populated from the repeater profile instead of the companion's own params.
|
|
||||||
void saveCurrentAsPreset(NodePrefs* p, const char* name) {
|
|
||||||
if (!p || !name || !name[0]) return;
|
|
||||||
int slot = -1;
|
|
||||||
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++)
|
|
||||||
if (strcmp(p->user_radio_presets[i].name, name) == 0) { slot = i; break; }
|
|
||||||
if (slot < 0)
|
|
||||||
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++)
|
|
||||||
if (!p->user_radio_presets[i].name[0]) { slot = i; break; }
|
|
||||||
if (slot < 0) slot = 0;
|
|
||||||
NodePrefs::UserRadioPreset& u = p->user_radio_presets[slot];
|
|
||||||
strncpy(u.name, name, sizeof(u.name) - 1);
|
|
||||||
u.name[sizeof(u.name) - 1] = '\0';
|
|
||||||
u.freq = p->repeater_freq; u.bw = p->repeater_bw; u.sf = p->repeater_sf; u.cr = p->repeater_cr;
|
|
||||||
_dirty = true;
|
|
||||||
_task->showAlert("Preset saved", 800);
|
|
||||||
}
|
|
||||||
|
|
||||||
void itemValue(int item, NodePrefs* p, char* buf, size_t n) const {
|
void itemValue(int item, NodePrefs* p, char* buf, size_t n) const {
|
||||||
if (!p) { strncpy(buf, "OFF", n); buf[n-1]=0; return; }
|
if (!p) { strncpy(buf, "OFF", n); buf[n-1]=0; return; }
|
||||||
switch (item) {
|
switch (item) {
|
||||||
case IT_REPEATER: strncpy(buf, p->client_repeat ? "ON" : "OFF", n); break;
|
case IT_REPEATER: strncpy(buf, p->client_repeat ? "ON" : "OFF", n); break;
|
||||||
case IT_NETWORK: strncpy(buf, p->repeater_use_profile ? "Custom" : "Current", n); break;
|
case IT_NETWORK: strncpy(buf, p->repeater_use_profile ? "Custom" : "Current", n); break;
|
||||||
case IT_RPRESET: strncpy(buf, rptPresetName(p), n); break;
|
case IT_RPRESET: strncpy(buf, _picker.currentName(p, rptTarget(p)), n); break;
|
||||||
case IT_RFREQ: snprintf(buf, n, "%.3f", p->repeater_freq); break;
|
case IT_RFREQ: snprintf(buf, n, "%.3f", p->repeater_freq); break;
|
||||||
case IT_RSF: snprintf(buf, n, "%d", (int)p->repeater_sf); break;
|
case IT_RSF: snprintf(buf, n, "%d", (int)p->repeater_sf); break;
|
||||||
case IT_RBW: snprintf(buf, n, "%.1f", p->repeater_bw); break;
|
case IT_RBW: snprintf(buf, n, "%.1f", p->repeater_bw); break;
|
||||||
@@ -191,45 +125,17 @@ class RepeaterScreen : public UIScreen {
|
|||||||
p->repeater_sf = p->sf; p->repeater_cr = p->cr;
|
p->repeater_sf = p->sf; p->repeater_cr = p->cr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layout: [0]="+ Save current...", [1..RADIO_PRESET_COUNT]=built-ins,
|
|
||||||
// [..+_preset_user_count]=saved user presets, ["- Delete preset..." if any].
|
|
||||||
void openPresetMenu(NodePrefs* p) {
|
|
||||||
_preset_deleting = false;
|
|
||||||
_preset_menu.begin("Repeater Preset", 6);
|
|
||||||
_preset_menu.addItem("+ Save current...");
|
|
||||||
for (int i = 0; i < RADIO_PRESET_COUNT; i++) _preset_menu.addItem(RADIO_PRESETS[i].name);
|
|
||||||
_preset_user_count = 0;
|
|
||||||
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++) {
|
|
||||||
if (!p->user_radio_presets[i].name[0]) continue;
|
|
||||||
_preset_menu.addItem(p->user_radio_presets[i].name);
|
|
||||||
_preset_user_slot[_preset_user_count++] = (uint8_t)i;
|
|
||||||
}
|
|
||||||
if (_preset_user_count > 0) _preset_menu.addItem("- Delete preset...");
|
|
||||||
int idx = currentPresetListIndex(p);
|
|
||||||
_preset_menu.setSelected(idx >= 0 ? idx : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reuses _preset_menu for a second-level list of just the saved user presets
|
|
||||||
// (their slot mapping in _preset_user_slot is still the one openPresetMenu()
|
|
||||||
// just built, since this is only reached from within that same popup).
|
|
||||||
void openDeletePresetMenu(NodePrefs* p) {
|
|
||||||
_preset_menu.begin("Delete Preset", 6);
|
|
||||||
for (int i = 0; i < _preset_user_count; i++)
|
|
||||||
_preset_menu.addItem(p->user_radio_presets[_preset_user_slot[i]].name);
|
|
||||||
_preset_deleting = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RepeaterScreen(UITask* task) : _task(task), _dirty(false), _sel(0), _scroll(0), _item_count(1) {}
|
RepeaterScreen(UITask* task) : _task(task), _dirty(false), _sel(0), _scroll(0), _item_count(1) {}
|
||||||
|
|
||||||
void enter() {
|
void enter() {
|
||||||
_dirty = false; _sel = 0; _scroll = 0;
|
_dirty = false; _sel = 0; _scroll = 0;
|
||||||
_preset_menu.active = false; _freq_editor.active = false;
|
_picker.menu.active = false; _freq_editor.active = false;
|
||||||
_preset_saving = false; _preset_deleting = false;
|
_picker.saving = false; _picker.deleting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int render(DisplayDriver& display) override {
|
int render(DisplayDriver& display) override {
|
||||||
if (_preset_saving) return _task->keyboard().render(display);
|
if (_picker.saving) return _task->keyboard().render(display);
|
||||||
|
|
||||||
NodePrefs* p = _task->getNodePrefs();
|
NodePrefs* p = _task->getNodePrefs();
|
||||||
buildItems(p);
|
buildItems(p);
|
||||||
@@ -271,61 +177,49 @@ public:
|
|||||||
}
|
}
|
||||||
drawScrollIndicator(display, start_y, visible * item_h, total, visible, _scroll);
|
drawScrollIndicator(display, start_y, visible * item_h, total, visible, _scroll);
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
if (_preset_menu.active) _preset_menu.render(display);
|
if (_picker.menu.active) _picker.menu.render(display);
|
||||||
return (_preset_menu.active || _freq_editor.active) ? 50 : 500;
|
return (_picker.menu.active || _freq_editor.active) ? 50 : 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool handleInput(char c) override {
|
bool handleInput(char c) override {
|
||||||
NodePrefs* p = _task->getNodePrefs();
|
NodePrefs* p = _task->getNodePrefs();
|
||||||
|
|
||||||
// Keyboard editing mode for naming a new saved preset
|
// Keyboard editing mode for naming a new saved preset
|
||||||
if (_preset_saving) {
|
if (_picker.saving) {
|
||||||
auto res = _task->keyboard().handleInput(c);
|
auto res = _task->keyboard().handleInput(c);
|
||||||
if (res == KeyboardWidget::DONE) {
|
if (res == KeyboardWidget::DONE) {
|
||||||
saveCurrentAsPreset(p, _task->keyboard().buf);
|
if (_picker.save(p, _task->keyboard().buf, rptTarget(p))) {
|
||||||
_preset_saving = false;
|
_dirty = true;
|
||||||
|
_task->showAlert("Preset saved", 800);
|
||||||
|
}
|
||||||
|
_picker.saving = false;
|
||||||
} else if (res == KeyboardWidget::CANCELLED) {
|
} else if (res == KeyboardWidget::CANCELLED) {
|
||||||
_preset_saving = false;
|
_picker.saving = false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modal overlays first.
|
// Modal overlays first.
|
||||||
if (_preset_menu.active) {
|
if (_picker.menu.active) {
|
||||||
auto res = _preset_menu.handleInput(c);
|
auto res = _picker.menu.handleInput(c);
|
||||||
if (res == PopupMenu::SELECTED && p) {
|
if (res == PopupMenu::SELECTED && p) {
|
||||||
int idx = _preset_menu.selectedIndex();
|
switch (_picker.onSelected(_picker.menu.selectedIndex(), p, rptTarget(p))) {
|
||||||
if (_preset_deleting) {
|
case RadioPresetPicker::START_SAVE:
|
||||||
if (idx >= 0 && idx < _preset_user_count) {
|
|
||||||
p->user_radio_presets[_preset_user_slot[idx]].name[0] = '\0';
|
|
||||||
_dirty = true;
|
|
||||||
_task->showAlert("Preset deleted", 800);
|
|
||||||
}
|
|
||||||
_preset_deleting = false;
|
|
||||||
} else {
|
|
||||||
int save_idx = 0;
|
|
||||||
int builtin_base = 1;
|
|
||||||
int user_base = builtin_base + RADIO_PRESET_COUNT;
|
|
||||||
int delete_idx = user_base + _preset_user_count;
|
|
||||||
if (idx == save_idx) {
|
|
||||||
_preset_saving = true;
|
|
||||||
_task->keyboard().begin("", (int)sizeof(p->user_radio_presets[0].name) - 1);
|
_task->keyboard().begin("", (int)sizeof(p->user_radio_presets[0].name) - 1);
|
||||||
} else if (idx >= builtin_base && idx < user_base) {
|
break;
|
||||||
const RadioPreset& r = RADIO_PRESETS[idx - builtin_base];
|
case RadioPresetPicker::APPLIED:
|
||||||
p->repeater_freq = r.freq; p->repeater_bw = r.bw; p->repeater_sf = r.sf; p->repeater_cr = r.cr;
|
|
||||||
the_mesh.applyRepeaterRadio(); // live if currently relaying on the profile
|
the_mesh.applyRepeaterRadio(); // live if currently relaying on the profile
|
||||||
_dirty = true;
|
_dirty = true;
|
||||||
} else if (idx >= user_base && idx < delete_idx) {
|
break;
|
||||||
const NodePrefs::UserRadioPreset& up = p->user_radio_presets[_preset_user_slot[idx - user_base]];
|
case RadioPresetPicker::DELETED:
|
||||||
p->repeater_freq = up.freq; p->repeater_bw = up.bw; p->repeater_sf = up.sf; p->repeater_cr = up.cr;
|
|
||||||
the_mesh.applyRepeaterRadio();
|
|
||||||
_dirty = true;
|
_dirty = true;
|
||||||
} else if (_preset_user_count > 0 && idx == delete_idx) {
|
_task->showAlert("Preset deleted", 800);
|
||||||
openDeletePresetMenu(p);
|
break;
|
||||||
}
|
case RadioPresetPicker::NONE:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (res == PopupMenu::CANCELLED) {
|
} else if (res == PopupMenu::CANCELLED) {
|
||||||
_preset_deleting = false;
|
_picker.deleting = false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -372,7 +266,7 @@ public:
|
|||||||
_dirty = true;
|
_dirty = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (item == IT_RPRESET && enter) { openPresetMenu(p); return true; }
|
if (item == IT_RPRESET && enter) { _picker.open(p, rptTarget(p), "Repeater Preset"); return true; }
|
||||||
if (item == IT_RFREQ && enter) {
|
if (item == IT_RFREQ && enter) {
|
||||||
float lo, hi; radio_driver.getFreqBounds(lo, hi);
|
float lo, hi; radio_driver.getFreqBounds(lo, hi);
|
||||||
_freq_editor.begin(p->repeater_freq, lo, hi, 3, 3);
|
_freq_editor.begin(p->repeater_freq, lo, hi, 3, 3);
|
||||||
@@ -383,7 +277,7 @@ public:
|
|||||||
if (left && p->repeater_sf > 5) { 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) {
|
if (item == IT_RBW) {
|
||||||
int idx = rptBwIndex(p);
|
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 (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 (left && idx > 0) { p->repeater_bw = LORA_BW_OPTS[idx - 1]; the_mesh.applyRepeaterRadio(); _dirty = true; return true; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "../Features.h"
|
#include "../Features.h"
|
||||||
#include "../RadioPresets.h"
|
#include "../RadioPresets.h"
|
||||||
#include "DigitEditor.h"
|
#include "DigitEditor.h"
|
||||||
|
#include "RadioPresetPicker.h"
|
||||||
|
|
||||||
class SettingsScreen : public UIScreen {
|
class SettingsScreen : public UIScreen {
|
||||||
UITask* _task;
|
UITask* _task;
|
||||||
@@ -115,77 +116,10 @@ class SettingsScreen : public UIScreen {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool matchesPreset(NodePrefs* p, float freq, float bw, uint8_t sf, uint8_t cr) {
|
// The companion's own radio fields, as the shared preset picker's target
|
||||||
return radioParamsMatchPreset(p->freq, p->bw, p->sf, p->cr, freq, bw, sf, cr);
|
// (Tools › Repeater points the same picker at the dedicated repeater profile).
|
||||||
}
|
RadioPresetPicker::Target radioTarget(NodePrefs* p) const {
|
||||||
|
return { &p->freq, &p->bw, &p->sf, &p->cr };
|
||||||
// Display name for the Preset row: a built-in preset, a saved user preset, or
|
|
||||||
// "Custom" if freq/sf/bw/cr don't exactly match any of them.
|
|
||||||
const char* currentPresetName() {
|
|
||||||
NodePrefs* p = _task->getNodePrefs();
|
|
||||||
if (!p) return "Custom";
|
|
||||||
for (int i = 0; i < RADIO_PRESET_COUNT; i++) {
|
|
||||||
const RadioPreset& r = RADIO_PRESETS[i];
|
|
||||||
if (matchesPreset(p, r.freq, r.bw, r.sf, r.cr)) return r.name;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++) {
|
|
||||||
const auto& u = p->user_radio_presets[i];
|
|
||||||
if (u.name[0] && matchesPreset(p, u.freq, u.bw, u.sf, u.cr)) return u.name;
|
|
||||||
}
|
|
||||||
return "Custom";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position of the current settings within the popup list built by
|
|
||||||
// openPresetMenu() below ("Save current..." at 0, then built-ins, then
|
|
||||||
// non-empty user slots in slot order) — or -1 ("Custom") if nothing matches.
|
|
||||||
int currentPresetListIndex() {
|
|
||||||
NodePrefs* p = _task->getNodePrefs();
|
|
||||||
if (!p) return -1;
|
|
||||||
for (int i = 0; i < RADIO_PRESET_COUNT; i++) {
|
|
||||||
const RadioPreset& r = RADIO_PRESETS[i];
|
|
||||||
if (matchesPreset(p, r.freq, r.bw, r.sf, r.cr)) return i + 1;
|
|
||||||
}
|
|
||||||
int pos = RADIO_PRESET_COUNT + 1;
|
|
||||||
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++) {
|
|
||||||
const auto& u = p->user_radio_presets[i];
|
|
||||||
if (!u.name[0]) continue;
|
|
||||||
if (matchesPreset(p, u.freq, u.bw, u.sf, u.cr)) return pos;
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the current freq/sf/bw/cr as a named user preset: overwrite a slot with
|
|
||||||
// the same name if one exists, else the first empty slot, else slot 0 (oldest).
|
|
||||||
void saveCurrentAsPreset(const char* name) {
|
|
||||||
NodePrefs* p = _task->getNodePrefs();
|
|
||||||
if (!p || !name || !name[0]) return;
|
|
||||||
int slot = -1;
|
|
||||||
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++)
|
|
||||||
if (strcmp(p->user_radio_presets[i].name, name) == 0) { slot = i; break; }
|
|
||||||
if (slot < 0)
|
|
||||||
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++)
|
|
||||||
if (!p->user_radio_presets[i].name[0]) { slot = i; break; }
|
|
||||||
if (slot < 0) slot = 0;
|
|
||||||
NodePrefs::UserRadioPreset& u = p->user_radio_presets[slot];
|
|
||||||
strncpy(u.name, name, sizeof(u.name) - 1);
|
|
||||||
u.name[sizeof(u.name) - 1] = '\0';
|
|
||||||
u.freq = p->freq; u.bw = p->bw; u.sf = p->sf; u.cr = p->cr;
|
|
||||||
_dirty = true;
|
|
||||||
_task->showAlert("Preset saved", 800);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nearest entry in LORA_BW_OPTS to the current bw (also used to step left/right).
|
|
||||||
int customBwIndex() {
|
|
||||||
NodePrefs* p = _task->getNodePrefs();
|
|
||||||
float bw = p ? p->bw : 62.5f;
|
|
||||||
int best = 0;
|
|
||||||
float best_diff = 1e9f;
|
|
||||||
for (int i = 0; i < LORA_BW_OPT_COUNT; i++) {
|
|
||||||
float diff = fabsf(bw - LORA_BW_OPTS[i]);
|
|
||||||
if (diff < best_diff) { best_diff = diff; best = i; }
|
|
||||||
}
|
|
||||||
return best;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value column start, pulled left by the scrollbar gutter so right-side
|
// Value column start, pulled left by the scrollbar gutter so right-side
|
||||||
@@ -568,7 +502,7 @@ class SettingsScreen : public UIScreen {
|
|||||||
display.print(buf);
|
display.print(buf);
|
||||||
} else if (item == RADIO_PRESET) {
|
} else if (item == RADIO_PRESET) {
|
||||||
display.print("Preset");
|
display.print("Preset");
|
||||||
const char* name = currentPresetName();
|
const char* name = p ? _picker.currentName(p, radioTarget(p)) : "Custom";
|
||||||
int xc = valCol(display);
|
int xc = valCol(display);
|
||||||
display.drawTextEllipsized(xc, y, display.width() - xc - _reserve, name);
|
display.drawTextEllipsized(xc, y, display.width() - xc - _reserve, name);
|
||||||
} else if (item == CUSTOM_FREQ) {
|
} else if (item == CUSTOM_FREQ) {
|
||||||
@@ -711,51 +645,15 @@ class SettingsScreen : public UIScreen {
|
|||||||
KeyboardWidget* _kb;
|
KeyboardWidget* _kb;
|
||||||
|
|
||||||
// Radio preset picker — names are too long for the value column, so Enter on
|
// Radio preset picker — names are too long for the value column, so Enter on
|
||||||
// RADIO_PRESET opens this as a full-width scrollable list instead of cycling.
|
// RADIO_PRESET opens it as a full-width scrollable list instead of cycling.
|
||||||
PopupMenu _preset_menu;
|
// Shared with Tools › Repeater (see RadioPresetPicker.h). _picker.saving means
|
||||||
// Maps a user-preset row's position in the open popup back to its slot in
|
// _kb is open to name a new preset, not a message slot.
|
||||||
// NodePrefs::user_radio_presets (empty slots are skipped when building the list).
|
RadioPresetPicker _picker;
|
||||||
uint8_t _preset_user_slot[NodePrefs::USER_RADIO_PRESET_MAX];
|
|
||||||
int _preset_user_count = 0;
|
|
||||||
bool _preset_saving = false; // _kb is open to name a new preset, not a msg slot
|
|
||||||
bool _preset_deleting = false; // _preset_menu is showing the delete sub-list
|
|
||||||
|
|
||||||
// Digit-by-digit Freq editor (see DigitEditor.h) — only this field uses it;
|
// 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.
|
// SF/BW/CR are small discrete sets where a plain left/right cycle is fine.
|
||||||
DigitEditor _freq_editor;
|
DigitEditor _freq_editor;
|
||||||
|
|
||||||
// Layout: [0]="+ Save current...", [1..RADIO_PRESET_COUNT]=built-ins,
|
|
||||||
// [..+_preset_user_count]=saved user presets, ["- Delete preset..." if any].
|
|
||||||
void openPresetMenu() {
|
|
||||||
NodePrefs* p = _task->getNodePrefs();
|
|
||||||
_preset_deleting = false;
|
|
||||||
_preset_menu.begin("Radio Preset", 6);
|
|
||||||
_preset_menu.addItem("+ Save current...");
|
|
||||||
for (int i = 0; i < RADIO_PRESET_COUNT; i++) _preset_menu.addItem(RADIO_PRESETS[i].name);
|
|
||||||
_preset_user_count = 0;
|
|
||||||
if (p) {
|
|
||||||
for (int i = 0; i < NodePrefs::USER_RADIO_PRESET_MAX; i++) {
|
|
||||||
if (!p->user_radio_presets[i].name[0]) continue;
|
|
||||||
_preset_menu.addItem(p->user_radio_presets[i].name);
|
|
||||||
_preset_user_slot[_preset_user_count++] = (uint8_t)i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (_preset_user_count > 0) _preset_menu.addItem("- Delete preset...");
|
|
||||||
int idx = currentPresetListIndex();
|
|
||||||
_preset_menu.setSelected(idx >= 0 ? idx : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reuses _preset_menu for a second-level list of just the saved user presets
|
|
||||||
// (their slot mapping in _preset_user_slot is still the one openPresetMenu()
|
|
||||||
// just built, since this is only reached from within that same popup).
|
|
||||||
void openDeletePresetMenu() {
|
|
||||||
NodePrefs* p = _task->getNodePrefs();
|
|
||||||
_preset_menu.begin("Delete Preset", 6);
|
|
||||||
for (int i = 0; i < _preset_user_count; i++)
|
|
||||||
_preset_menu.addItem(p->user_radio_presets[_preset_user_slot[i]].name);
|
|
||||||
_preset_deleting = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SettingsScreen(UITask* task, KeyboardWidget* kb)
|
SettingsScreen(UITask* task, KeyboardWidget* kb)
|
||||||
: _task(task), _kb(kb), _selected(SECTION_DISPLAY), _scroll(0), _visible(4), _dirty(false), _edit_slot(-1) {
|
: _task(task), _kb(kb), _selected(SECTION_DISPLAY), _scroll(0), _visible(4), _dirty(false), _edit_slot(-1) {
|
||||||
@@ -775,7 +673,7 @@ public:
|
|||||||
int render(DisplayDriver& display) override {
|
int render(DisplayDriver& display) override {
|
||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
|
|
||||||
if (_edit_slot >= 0 || _preset_saving) {
|
if (_edit_slot >= 0 || _picker.saving) {
|
||||||
return _kb->render(display);
|
return _kb->render(display);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,7 +690,7 @@ public:
|
|||||||
|
|
||||||
drawScrollIndicator(display, start_y, _visible * item_h, _vis_count, _visible, _scroll);
|
drawScrollIndicator(display, start_y, _visible * item_h, _vis_count, _visible, _scroll);
|
||||||
|
|
||||||
if (_preset_menu.active) _preset_menu.render(display);
|
if (_picker.menu.active) _picker.menu.render(display);
|
||||||
|
|
||||||
return 2000;
|
return 2000;
|
||||||
}
|
}
|
||||||
@@ -829,53 +727,41 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard editing mode for naming a new saved preset
|
// Keyboard editing mode for naming a new saved preset
|
||||||
if (_preset_saving) {
|
if (_picker.saving) {
|
||||||
auto res = _kb->handleInput(c);
|
auto res = _kb->handleInput(c);
|
||||||
if (res == KeyboardWidget::DONE) {
|
if (res == KeyboardWidget::DONE) {
|
||||||
saveCurrentAsPreset(_kb->buf);
|
if (p && _picker.save(p, _kb->buf, radioTarget(p))) {
|
||||||
_preset_saving = false;
|
_dirty = true;
|
||||||
|
_task->showAlert("Preset saved", 800);
|
||||||
|
}
|
||||||
|
_picker.saving = false;
|
||||||
} else if (res == KeyboardWidget::CANCELLED) {
|
} else if (res == KeyboardWidget::CANCELLED) {
|
||||||
_preset_saving = false;
|
_picker.saving = false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Radio preset popup (and its "delete a saved preset" sub-list)
|
// Radio preset popup (and its "delete a saved preset" sub-list)
|
||||||
if (_preset_menu.active) {
|
if (_picker.menu.active) {
|
||||||
auto res = _preset_menu.handleInput(c);
|
auto res = _picker.menu.handleInput(c);
|
||||||
if (res == PopupMenu::SELECTED && p) {
|
if (res == PopupMenu::SELECTED && p) {
|
||||||
int idx = _preset_menu.selectedIndex();
|
switch (_picker.onSelected(_picker.menu.selectedIndex(), p, radioTarget(p))) {
|
||||||
if (_preset_deleting) {
|
case RadioPresetPicker::START_SAVE:
|
||||||
if (idx >= 0 && idx < _preset_user_count) {
|
_kb->begin("", (int)sizeof(p->user_radio_presets[0].name) - 1);
|
||||||
p->user_radio_presets[_preset_user_slot[idx]].name[0] = '\0';
|
break;
|
||||||
|
case RadioPresetPicker::APPLIED:
|
||||||
|
_task->applyRadioParams();
|
||||||
|
_dirty = true;
|
||||||
|
break;
|
||||||
|
case RadioPresetPicker::DELETED:
|
||||||
_dirty = true;
|
_dirty = true;
|
||||||
_task->showAlert("Preset deleted", 800);
|
_task->showAlert("Preset deleted", 800);
|
||||||
}
|
break;
|
||||||
_preset_deleting = false;
|
case RadioPresetPicker::NONE:
|
||||||
} else {
|
break;
|
||||||
int save_idx = 0;
|
|
||||||
int builtin_base = 1;
|
|
||||||
int user_base = builtin_base + RADIO_PRESET_COUNT;
|
|
||||||
int delete_idx = user_base + _preset_user_count;
|
|
||||||
if (idx == save_idx) {
|
|
||||||
_preset_saving = true;
|
|
||||||
_kb->begin("", (int)sizeof(p->user_radio_presets[0].name) - 1);
|
|
||||||
} else if (idx >= builtin_base && idx < user_base) {
|
|
||||||
const RadioPreset& r = RADIO_PRESETS[idx - builtin_base];
|
|
||||||
p->freq = r.freq; p->bw = r.bw; p->sf = r.sf; p->cr = r.cr;
|
|
||||||
_task->applyRadioParams();
|
|
||||||
_dirty = true;
|
|
||||||
} else if (idx >= user_base && idx < delete_idx) {
|
|
||||||
const NodePrefs::UserRadioPreset& u = p->user_radio_presets[_preset_user_slot[idx - user_base]];
|
|
||||||
p->freq = u.freq; p->bw = u.bw; p->sf = u.sf; p->cr = u.cr;
|
|
||||||
_task->applyRadioParams();
|
|
||||||
_dirty = true;
|
|
||||||
} else if (_preset_user_count > 0 && idx == delete_idx) {
|
|
||||||
openDeletePresetMenu();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (res == PopupMenu::CANCELLED) {
|
} else if (res == PopupMenu::CANCELLED) {
|
||||||
_preset_deleting = false;
|
_picker.deleting = false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -972,7 +858,7 @@ public:
|
|||||||
if (left && p->tx_power_dbm > 2) { p->tx_power_dbm--; _task->applyTxPower(); _dirty = true; return true; }
|
if (left && p->tx_power_dbm > 2) { p->tx_power_dbm--; _task->applyTxPower(); _dirty = true; return true; }
|
||||||
}
|
}
|
||||||
if (_selected == RADIO_PRESET && p && enter) {
|
if (_selected == RADIO_PRESET && p && enter) {
|
||||||
openPresetMenu();
|
_picker.open(p, radioTarget(p), "Radio Preset");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Enter Freq's digit-by-digit editor (see DigitEditor.h). Bounds come from
|
// Enter Freq's digit-by-digit editor (see DigitEditor.h). Bounds come from
|
||||||
@@ -989,7 +875,7 @@ public:
|
|||||||
if (left && p->sf > 5) { 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) {
|
if (_selected == CUSTOM_BW && p) {
|
||||||
int idx = customBwIndex();
|
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 (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 (left && idx > 0) { p->bw = LORA_BW_OPTS[idx - 1]; _task->applyRadioParams(); _dirty = true; return true; }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user