mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
fix(ui): standardize context menu cycling + PopupMenu height clamping
PopupMenu: - _cap now uses max_by_height as a hard ceiling (never draw outside screen). Previously max(_visible, max_by_height) let callers exceed the screen on OLED 64px. Now: OLED→4 items, landscape e-ink→10, portrait e-ink→22. QuickMsgScreen: - Contact context menu: Notif and Melody cycle with LEFT/RIGHT in-place; ENTER closes without re-cycling. - Channel context menu: Notif, Melody and Fav same pattern. RingtoneEditorScreen: - Migrated from bespoke menu to PopupMenu (same pattern as everywhere else). - Duration (1/4…1/32) and BPM (60…180) are now single rows that cycle with LEFT/RIGHT; separate BPM+/BPM- rows removed. - Fixed _menu_dur_label buffer too small for "Duration: 1/16" (14→16 bytes). NearbyScreen: - Removed redundant "Back" item from context menu (Cancel key navigates back). TrailScreen: - Grid toggle responds to LEFT/RIGHT in addition to ENTER. - Export labels: "Export GPX (live/saved)" → "Export (live/saved)" to fit PM_BW. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -516,21 +516,16 @@ public:
|
||||
// ── context menu ─────────────────────────────────────────────────────────
|
||||
if (_ctx_menu.active) {
|
||||
auto res = _ctx_menu.handleInput(c);
|
||||
if (res == PopupMenu::SELECTED) {
|
||||
if (_ctx_menu.selectedIndex() == 0)
|
||||
enterDiscoverMode();
|
||||
else
|
||||
_task->gotoToolsScreen();
|
||||
}
|
||||
if (res == PopupMenu::SELECTED)
|
||||
enterDiscoverMode();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── list view ────────────────────────────────────────────────────────────
|
||||
if (c == KEY_CANCEL) { _task->gotoToolsScreen(); return true; }
|
||||
if (c == KEY_CONTEXT_MENU) {
|
||||
_ctx_menu.begin("Options", 2);
|
||||
_ctx_menu.begin("Options", 1);
|
||||
_ctx_menu.addItem("Discover nearby");
|
||||
_ctx_menu.addItem("Back");
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_UP && _sel > 0) {
|
||||
|
||||
@@ -35,11 +35,12 @@ struct PopupMenu {
|
||||
}
|
||||
|
||||
int render(DisplayDriver& display) {
|
||||
// On tall displays (portrait e-ink) show as many items as fit;
|
||||
// on small displays fall back to the caller-specified _visible cap.
|
||||
// Hard ceiling: never show more items than physically fit on screen.
|
||||
// On tall displays (portrait e-ink) this expands beyond _visible;
|
||||
// on small displays (OLED 64px) it clamps below _visible.
|
||||
int max_by_height = (display.height() - PM_BY - 12) / PM_ITEM_H;
|
||||
if (max_by_height < 1) max_by_height = 1;
|
||||
_cap = (max_by_height > _visible) ? max_by_height : _visible;
|
||||
_cap = max_by_height;
|
||||
int vis = (_count < _cap) ? _count : _cap;
|
||||
int bh = 12 + vis * PM_ITEM_H;
|
||||
|
||||
|
||||
@@ -1021,6 +1021,33 @@ public:
|
||||
} else if (_phase == CONTACT_PICK) {
|
||||
// Context menu consumes all input while open
|
||||
if (_ctx_menu.active) {
|
||||
// LEFT/RIGHT cycle Notif/Melody in-place (menu stays open).
|
||||
if (!_pin_picker_active && _num_contacts > 0) {
|
||||
bool left = (c == KEY_LEFT || c == KEY_PREV);
|
||||
bool right = (c == KEY_RIGHT || c == KEY_NEXT);
|
||||
if (left || right) {
|
||||
static const char* NOTIF_LABELS[] = { "default", "OFF", "ON" };
|
||||
static const char* ML[] = { "global", "M1", "M2" };
|
||||
ContactInfo ci;
|
||||
if (the_mesh.getContactByIdx(_sorted[_contact_sel], ci)) {
|
||||
int sel = _ctx_menu.selectedIndex();
|
||||
if (sel == 1) {
|
||||
uint8_t v = dmNotifState(ci.id.pub_key);
|
||||
v = right ? (v + 1) % 3 : (v + 2) % 3;
|
||||
setDmNotifState(ci.id.pub_key, v);
|
||||
snprintf(_ctx_notif_item, sizeof(_ctx_notif_item), "Notif: %s", NOTIF_LABELS[v]);
|
||||
_ctx_dirty = true;
|
||||
} else if (sel == 2) {
|
||||
uint8_t v = dmMelodySlot(ci.id.pub_key);
|
||||
v = right ? (v + 1) % 3 : (v + 2) % 3;
|
||||
setDmMelody(ci.id.pub_key, v);
|
||||
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s", ML[v]);
|
||||
_ctx_dirty = true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
auto res = _ctx_menu.handleInput(c);
|
||||
if (_pin_picker_active) {
|
||||
// Slot picker sub-menu: index 0..FAVOURITES_COUNT-1 maps directly to slot.
|
||||
@@ -1041,17 +1068,10 @@ public:
|
||||
if (res == PopupMenu::SELECTED && _num_contacts > 0) {
|
||||
ContactInfo ci;
|
||||
if (the_mesh.getContactByIdx(_sorted[_contact_sel], ci)) {
|
||||
if (_ctx_menu.selectedIndex() == 0) {
|
||||
int sel = _ctx_menu.selectedIndex();
|
||||
if (sel == 0) {
|
||||
_task->clearDMUnread(ci.id.pub_key);
|
||||
} else if (_ctx_menu.selectedIndex() == 1) {
|
||||
uint8_t nstate = dmNotifState(ci.id.pub_key);
|
||||
setDmNotifState(ci.id.pub_key, (nstate + 1) % 3);
|
||||
_ctx_dirty = true;
|
||||
} else if (_ctx_menu.selectedIndex() == 2) {
|
||||
uint8_t slot = dmMelodySlot(ci.id.pub_key);
|
||||
setDmMelody(ci.id.pub_key, (slot + 1) % 3);
|
||||
_ctx_dirty = true;
|
||||
} else if (_ctx_menu.selectedIndex() == 3) {
|
||||
} else if (sel == 3) {
|
||||
// Pin / Unpin
|
||||
int pinned_slot = _task->findFavouriteSlot(ci.id.pub_key);
|
||||
if (pinned_slot >= 0) {
|
||||
@@ -1061,12 +1081,10 @@ public:
|
||||
snprintf(alert, sizeof(alert), "Unpinned (slot %d)", pinned_slot + 1);
|
||||
_task->showAlert(alert, 800);
|
||||
} else {
|
||||
// Open slot picker. Each label shows "Slot N: <name>" or "Slot N: empty".
|
||||
for (int s = 0; s < NodePrefs::FAVOURITES_COUNT; s++) {
|
||||
if (_task->isFavouriteSlotEmpty(s)) {
|
||||
snprintf(_pin_slot_labels[s], sizeof(_pin_slot_labels[s]), "Slot %d: empty", s + 1);
|
||||
} else {
|
||||
// Resolve current occupant name by walking contacts.
|
||||
char nm[16] = "?";
|
||||
NodePrefs* p = _task->getNodePrefs();
|
||||
if (p) {
|
||||
@@ -1088,6 +1106,7 @@ public:
|
||||
_pin_picker_active = true;
|
||||
}
|
||||
}
|
||||
// sel == 1 (Notif) and sel == 2 (Melody): already cycled via LEFT/RIGHT; ENTER just closes.
|
||||
}
|
||||
}
|
||||
if (res != PopupMenu::NONE && _ctx_dirty) {
|
||||
@@ -1140,29 +1159,49 @@ public:
|
||||
} else if (_phase == CHANNEL_PICK) {
|
||||
// Context menu consumes all input while open
|
||||
if (_ctx_menu.active) {
|
||||
// LEFT/RIGHT cycle Notif/Melody/Fav in-place (menu stays open).
|
||||
if (_num_channels > 0) {
|
||||
bool left = (c == KEY_LEFT || c == KEY_PREV);
|
||||
bool right = (c == KEY_RIGHT || c == KEY_NEXT);
|
||||
if (left || right) {
|
||||
static const char* NOTIF_LABELS[] = { "default", "OFF", "ON" };
|
||||
static const char* ML[] = { "global", "M1", "M2" };
|
||||
uint8_t ch_idx = _channel_indices[_channel_sel];
|
||||
int sel = _ctx_menu.selectedIndex();
|
||||
if (sel == 1) {
|
||||
uint8_t v = chNotifState(ch_idx);
|
||||
v = right ? (v + 1) % 3 : (v + 2) % 3;
|
||||
setChNotifState(ch_idx, v);
|
||||
snprintf(_ctx_notif_item, sizeof(_ctx_notif_item), "Notif: %s", NOTIF_LABELS[v]);
|
||||
_ctx_dirty = true;
|
||||
} else if (sel == 2) {
|
||||
uint8_t v = chNotifMelody(ch_idx);
|
||||
v = right ? (v + 1) % 3 : (v + 2) % 3;
|
||||
setChNotifMelody(ch_idx, v);
|
||||
snprintf(_ctx_melody_item, sizeof(_ctx_melody_item), "Melody: %s", ML[v]);
|
||||
_ctx_dirty = true;
|
||||
} else if (sel == 3) {
|
||||
NodePrefs* p2 = _task->getNodePrefs();
|
||||
if (p2) {
|
||||
p2->ch_fav_bitmask ^= (1ULL << ch_idx);
|
||||
bool is_fav = (p2->ch_fav_bitmask & (1ULL << ch_idx));
|
||||
snprintf(_ctx_ch_fav_item, sizeof(_ctx_ch_fav_item), is_fav ? "Fav: yes" : "Fav: no");
|
||||
_ctx_dirty = true;
|
||||
buildChannelList();
|
||||
if (_channel_sel >= _num_channels) _channel_sel = _num_channels > 0 ? _num_channels - 1 : 0;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
auto res = _ctx_menu.handleInput(c);
|
||||
if (res == PopupMenu::SELECTED && _num_channels > 0) {
|
||||
uint8_t ch_idx = _channel_indices[_channel_sel];
|
||||
if (_ctx_menu.selectedIndex() == 0) {
|
||||
int sel = _ctx_menu.selectedIndex();
|
||||
if (sel == 0) {
|
||||
_ch_unread[ch_idx] = 0;
|
||||
} else if (_ctx_menu.selectedIndex() == 1) {
|
||||
uint8_t nstate = chNotifState(ch_idx);
|
||||
setChNotifState(ch_idx, (nstate + 1) % 3);
|
||||
_ctx_dirty = true;
|
||||
} else if (_ctx_menu.selectedIndex() == 2) {
|
||||
uint8_t slot = chNotifMelody(ch_idx);
|
||||
setChNotifMelody(ch_idx, (slot + 1) % 3);
|
||||
_ctx_dirty = true;
|
||||
} else {
|
||||
// Toggle favourite
|
||||
NodePrefs* p2 = _task->getNodePrefs();
|
||||
if (p2) {
|
||||
p2->ch_fav_bitmask ^= (1ULL << ch_idx);
|
||||
_ctx_dirty = true;
|
||||
buildChannelList(); // refilter if ch_fav_only is active
|
||||
if (_channel_sel >= _num_channels) _channel_sel = _num_channels > 0 ? _num_channels - 1 : 0;
|
||||
}
|
||||
}
|
||||
// sel 1/2/3 already handled by LEFT/RIGHT; ENTER just closes.
|
||||
}
|
||||
if (res != PopupMenu::NONE && _ctx_dirty) {
|
||||
the_mesh.savePrefs(); _ctx_dirty = false;
|
||||
|
||||
@@ -6,8 +6,10 @@ class RingtoneEditorScreen : public UIScreen {
|
||||
UITask* _task;
|
||||
NodePrefs* _prefs;
|
||||
|
||||
static const int MAX_NOTES = 32;
|
||||
static const int MENU_VISIBLE = 5;
|
||||
static const int MAX_NOTES = 32;
|
||||
|
||||
// Menu item indices
|
||||
enum MenuIdx { MI_PLAY=0, MI_SWITCH, MI_DURATION, MI_BPM, MI_INSERT, MI_DELETE, MI_SAVE, MI_DISCARD, MI_COUNT };
|
||||
|
||||
int _visible_notes = 7; // updated in render(); used by clampScroll()
|
||||
|
||||
@@ -22,16 +24,12 @@ class RingtoneEditorScreen : public UIScreen {
|
||||
int _slot; // 0=melody1, 1=melody2
|
||||
int _cursor;
|
||||
int _scroll;
|
||||
bool _menu_open;
|
||||
int _menu_sel;
|
||||
int _menu_scroll;
|
||||
PopupMenu _menu;
|
||||
char _play_buf[220]; // persistent RTTTL buffer — library holds a pointer into it
|
||||
|
||||
enum MenuOpt {
|
||||
M_PLAY_STOP, M_SWITCH, M_DURATION, M_BPM_UP, M_BPM_DOWN,
|
||||
M_INSERT, M_DELETE, M_SAVE, M_DISCARD, M_COUNT
|
||||
};
|
||||
static const char* MENU_LABELS[M_COUNT];
|
||||
char _menu_play_label[8];
|
||||
char _menu_slot_label[16];
|
||||
char _menu_dur_label[16];
|
||||
char _menu_bpm_label[10];
|
||||
|
||||
static uint8_t notePitch(uint8_t b) { return b & 0x07; }
|
||||
static uint8_t noteOctave(uint8_t b) { return ((b >> 3) & 0x03) + 4; }
|
||||
@@ -68,11 +66,26 @@ public:
|
||||
uint8_t rlen = _prefs ? (s2 ? _prefs->ringtone2_len : _prefs->ringtone_len) : 0;
|
||||
_len = (rlen <= (uint8_t)MAX_NOTES) ? rlen : 0;
|
||||
if (_prefs) memcpy(_notes, s2 ? _prefs->ringtone2_notes : _prefs->ringtone_notes, sizeof(_notes));
|
||||
_cursor = 0;
|
||||
_scroll = 0;
|
||||
_menu_open = false;
|
||||
_menu_sel = 0;
|
||||
_menu_scroll = 0;
|
||||
_cursor = 0;
|
||||
_scroll = 0;
|
||||
_menu.active = false;
|
||||
}
|
||||
|
||||
void openMenu() {
|
||||
snprintf(_menu_play_label, sizeof(_menu_play_label), "%s", _task->isMelodyPlaying() ? "Stop" : "Play");
|
||||
snprintf(_menu_slot_label, sizeof(_menu_slot_label), "Melody %d", (_slot == 0) ? 2 : 1);
|
||||
uint8_t di = (_cursor < _len) ? noteDurIdx(_notes[_cursor]) : 0;
|
||||
snprintf(_menu_dur_label, sizeof(_menu_dur_label), "Duration: %s", DUR_LABELS[di]);
|
||||
snprintf(_menu_bpm_label, sizeof(_menu_bpm_label), "BPM: %u", BPM_OPTS[_bpm_idx]);
|
||||
_menu.begin("Options", 5);
|
||||
_menu.addItem(_menu_play_label);
|
||||
_menu.addItem(_menu_slot_label);
|
||||
_menu.addItem(_menu_dur_label);
|
||||
_menu.addItem(_menu_bpm_label);
|
||||
_menu.addItem("Insert");
|
||||
_menu.addItem("Delete");
|
||||
_menu.addItem("Save & Exit");
|
||||
_menu.addItem("Discard");
|
||||
}
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
@@ -144,34 +157,7 @@ public:
|
||||
display.setCursor(0, bottom_y);
|
||||
display.print("ENT:oct MENU:opts");
|
||||
|
||||
if (_menu_open) {
|
||||
const int menu_ih = display.lineStep();
|
||||
const int mw = 100, mh = MENU_VISIBLE * menu_ih + 4;
|
||||
int mx = (display.width() - mw) / 2;
|
||||
int my = (display.height() - mh) / 2;
|
||||
display.setColor(DisplayDriver::DARK);
|
||||
display.fillRect(mx, my, mw, mh);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(mx, my, mw, mh);
|
||||
for (int i = 0; i < MENU_VISIBLE; i++) {
|
||||
int item = _menu_scroll + i;
|
||||
if (item >= M_COUNT) break;
|
||||
int iy = my + 2 + i * menu_ih;
|
||||
bool sel = (item == _menu_sel);
|
||||
display.drawSelectionRow(mx + 1, iy - 1, mw - 2, menu_ih, sel);
|
||||
display.setCursor(mx + 4, iy);
|
||||
if (item == M_PLAY_STOP)
|
||||
display.print(_task->isMelodyPlaying() ? "Stop" : "Play");
|
||||
else if (item == M_SWITCH)
|
||||
display.print(_slot == 0 ? "Edit Melody 2" : "Edit Melody 1");
|
||||
else
|
||||
display.print(MENU_LABELS[item]);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
}
|
||||
int cw = display.getCharWidth();
|
||||
if (_menu_scroll > 0) { display.setCursor(mx + mw - cw - 1, my + 2); display.print("^"); }
|
||||
if (_menu_scroll + MENU_VISIBLE < M_COUNT) { display.setCursor(mx + mw - cw - 1, my + mh - menu_ih); display.print("v"); }
|
||||
}
|
||||
if (_menu.active) _menu.render(display);
|
||||
|
||||
return 200;
|
||||
}
|
||||
@@ -185,90 +171,84 @@ public:
|
||||
bool menu_key = (c == KEY_CONTEXT_MENU);
|
||||
bool cancel = (c == KEY_CANCEL);
|
||||
|
||||
if (_menu_open) {
|
||||
if (cancel) { _menu_open = false; return true; }
|
||||
if (up && _menu_sel > 0) {
|
||||
_menu_sel--;
|
||||
if (_menu_sel < _menu_scroll) _menu_scroll = _menu_sel;
|
||||
if (_menu.active) {
|
||||
// LEFT/RIGHT cycle Duration and BPM in-place, menu stays open.
|
||||
if (left || right) {
|
||||
int sel = _menu.selectedIndex();
|
||||
if (sel == MI_DURATION && _cursor < _len) {
|
||||
uint8_t p = notePitch(_notes[_cursor]);
|
||||
uint8_t o = noteOctave(_notes[_cursor]);
|
||||
uint8_t di = noteDurIdx(_notes[_cursor]);
|
||||
di = right ? (di + 1) & 0x03 : (di + 3) & 0x03;
|
||||
_notes[_cursor] = packNote(p, o, di);
|
||||
snprintf(_menu_dur_label, sizeof(_menu_dur_label), "Duration: %s", DUR_LABELS[di]);
|
||||
} else if (sel == MI_BPM) {
|
||||
if (right && _bpm_idx < 4) _bpm_idx++;
|
||||
else if (left && _bpm_idx > 0) _bpm_idx--;
|
||||
snprintf(_menu_bpm_label, sizeof(_menu_bpm_label), "BPM: %u", BPM_OPTS[_bpm_idx]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (down && _menu_sel < M_COUNT - 1) {
|
||||
_menu_sel++;
|
||||
if (_menu_sel >= _menu_scroll + MENU_VISIBLE) _menu_scroll = _menu_sel - MENU_VISIBLE + 1;
|
||||
return true;
|
||||
}
|
||||
if (!enter) return true;
|
||||
|
||||
_menu_open = false;
|
||||
switch ((MenuOpt)_menu_sel) {
|
||||
case M_PLAY_STOP:
|
||||
if (_task->isMelodyPlaying()) {
|
||||
auto res = _menu.handleInput(c);
|
||||
if (res == PopupMenu::SELECTED) {
|
||||
switch ((MenuIdx)_menu.selectedIndex()) {
|
||||
case MI_PLAY:
|
||||
if (_task->isMelodyPlaying()) { _task->stopMelody(); }
|
||||
else if (_len > 0) { buildRTTTL(); _task->playMelody(_play_buf); }
|
||||
break;
|
||||
case MI_SWITCH:
|
||||
_task->stopMelody();
|
||||
} else if (_len > 0) {
|
||||
buildRTTTL();
|
||||
_task->playMelody(_play_buf);
|
||||
}
|
||||
break;
|
||||
case M_SWITCH:
|
||||
_task->stopMelody();
|
||||
this->enter(1 - _slot);
|
||||
break;
|
||||
case M_DURATION:
|
||||
if (_cursor < _len) {
|
||||
uint8_t p = notePitch(_notes[_cursor]);
|
||||
uint8_t o = noteOctave(_notes[_cursor]);
|
||||
uint8_t di = (noteDurIdx(_notes[_cursor]) + 1) & 0x03;
|
||||
_notes[_cursor] = packNote(p, o, di);
|
||||
}
|
||||
break;
|
||||
case M_BPM_UP: if (_bpm_idx < 4) _bpm_idx++; break;
|
||||
case M_BPM_DOWN: if (_bpm_idx > 0) _bpm_idx--; break;
|
||||
case M_INSERT:
|
||||
if (_len < MAX_NOTES) {
|
||||
int ins = (_cursor < _len) ? _cursor + 1 : _cursor;
|
||||
for (int i = _len; i > ins; i--) _notes[i] = _notes[i - 1];
|
||||
_notes[ins] = packNote(1, 5, 1);
|
||||
_len++;
|
||||
_cursor = ins;
|
||||
clampScroll();
|
||||
}
|
||||
break;
|
||||
case M_DELETE:
|
||||
if (_len > 0 && _cursor < _len) {
|
||||
for (int i = _cursor; i < _len - 1; i++) _notes[i] = _notes[i + 1];
|
||||
_len--;
|
||||
if (_cursor >= _len && _len > 0) _cursor = _len - 1;
|
||||
else if (_len == 0) _cursor = 0;
|
||||
clampScroll();
|
||||
}
|
||||
break;
|
||||
case M_SAVE:
|
||||
if (_prefs) {
|
||||
if (_slot == 1) {
|
||||
_prefs->ringtone2_bpm_idx = _bpm_idx;
|
||||
_prefs->ringtone2_len = _len;
|
||||
memcpy(_prefs->ringtone2_notes, _notes, sizeof(_notes));
|
||||
} else {
|
||||
_prefs->ringtone_bpm_idx = _bpm_idx;
|
||||
_prefs->ringtone_len = _len;
|
||||
memcpy(_prefs->ringtone_notes, _notes, sizeof(_notes));
|
||||
this->enter(1 - _slot);
|
||||
break;
|
||||
case MI_DURATION: break; // already handled by LEFT/RIGHT
|
||||
case MI_BPM: break; // already handled by LEFT/RIGHT
|
||||
case MI_INSERT:
|
||||
if (_len < MAX_NOTES) {
|
||||
int ins = (_cursor < _len) ? _cursor + 1 : _cursor;
|
||||
for (int i = _len; i > ins; i--) _notes[i] = _notes[i - 1];
|
||||
_notes[ins] = packNote(1, 5, 1);
|
||||
_len++;
|
||||
_cursor = ins;
|
||||
clampScroll();
|
||||
}
|
||||
the_mesh.savePrefs();
|
||||
}
|
||||
_task->stopMelody();
|
||||
_task->gotoToolsScreen();
|
||||
break;
|
||||
case M_DISCARD:
|
||||
_task->stopMelody();
|
||||
_task->gotoToolsScreen();
|
||||
break;
|
||||
default: break;
|
||||
break;
|
||||
case MI_DELETE:
|
||||
if (_len > 0 && _cursor < _len) {
|
||||
for (int i = _cursor; i < _len - 1; i++) _notes[i] = _notes[i + 1];
|
||||
_len--;
|
||||
if (_cursor >= _len && _len > 0) _cursor = _len - 1;
|
||||
else if (_len == 0) _cursor = 0;
|
||||
clampScroll();
|
||||
}
|
||||
break;
|
||||
case MI_SAVE:
|
||||
if (_prefs) {
|
||||
if (_slot == 1) {
|
||||
_prefs->ringtone2_bpm_idx = _bpm_idx;
|
||||
_prefs->ringtone2_len = _len;
|
||||
memcpy(_prefs->ringtone2_notes, _notes, sizeof(_notes));
|
||||
} else {
|
||||
_prefs->ringtone_bpm_idx = _bpm_idx;
|
||||
_prefs->ringtone_len = _len;
|
||||
memcpy(_prefs->ringtone_notes, _notes, sizeof(_notes));
|
||||
}
|
||||
the_mesh.savePrefs();
|
||||
}
|
||||
_task->stopMelody();
|
||||
_task->gotoToolsScreen();
|
||||
break;
|
||||
case MI_DISCARD:
|
||||
_task->stopMelody();
|
||||
_task->gotoToolsScreen();
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cancel) { _task->stopMelody(); _task->gotoToolsScreen(); return true; }
|
||||
if (menu_key) { _menu_open = true; _menu_sel = 0; _menu_scroll = 0; return true; }
|
||||
if (menu_key) { openMenu(); return true; }
|
||||
|
||||
if (left && _cursor > 0) { _cursor--; clampScroll(); return true; }
|
||||
if (right) {
|
||||
@@ -316,6 +296,3 @@ const uint16_t RingtoneEditorScreen::BPM_OPTS[5] = { 60, 90, 120, 150, 180 };
|
||||
const uint8_t RingtoneEditorScreen::DUR_VALS[4] = { 4, 8, 16, 32 };
|
||||
const char* RingtoneEditorScreen::DUR_LABELS[4] = { "1/4", "1/8", "1/16", "1/32" };
|
||||
const char RingtoneEditorScreen::PITCH_NAMES[8] = { 'p', 'c', 'd', 'e', 'f', 'g', 'a', 'b' };
|
||||
const char* RingtoneEditorScreen::MENU_LABELS[RingtoneEditorScreen::M_COUNT] = {
|
||||
"Play/Stop", "Switch Melody", "Duration", "BPM+", "BPM-", "Insert", "Delete", "Save & Exit", "Discard"
|
||||
};
|
||||
|
||||
@@ -80,6 +80,7 @@ public:
|
||||
int dir = (c == KEY_RIGHT || c == KEY_NEXT) ? 1 : -1;
|
||||
if (_act_map[idx] == ACT_MIN_DIST && p) { cycleMinDelta(p, dir); _cfg_dirty = true; refreshActionLabels(); return true; }
|
||||
if (_act_map[idx] == ACT_UNITS && p) { cycleUnits(p, dir); _cfg_dirty = true; refreshActionLabels(); return true; }
|
||||
if (_act_map[idx] == ACT_GRID) { _map_grid = !_map_grid; refreshActionLabels(); return true; }
|
||||
}
|
||||
return true; // swallow on action rows
|
||||
}
|
||||
@@ -229,10 +230,10 @@ private:
|
||||
_act_map[_act_count++] = ACT_LOAD; _action_menu.addItem("Load trail");
|
||||
}
|
||||
if (!_store->empty()) {
|
||||
_act_map[_act_count++] = ACT_EXPORT; _action_menu.addItem("Export GPX (live)");
|
||||
_act_map[_act_count++] = ACT_EXPORT; _action_menu.addItem("Export (live)");
|
||||
}
|
||||
if (saved) {
|
||||
_act_map[_act_count++] = ACT_EXPORT_SAVED; _action_menu.addItem("Export GPX (saved)");
|
||||
_act_map[_act_count++] = ACT_EXPORT_SAVED; _action_menu.addItem("Export (saved)");
|
||||
}
|
||||
if (!_store->empty()) {
|
||||
_act_map[_act_count++] = ACT_RESET; _action_menu.addItem("Reset trail");
|
||||
|
||||
Reference in New Issue
Block a user