feat(color): move model notes to model setup group in quick menu (#6737)

This commit is contained in:
philmoz
2025-10-30 16:23:37 +10:00
committed by GitHub
parent 5202c5c283
commit 4101d3fe0b
9 changed files with 315 additions and 270 deletions
@@ -34,6 +34,7 @@ SwitchWarnDialog::SwitchWarnDialog() :
void SwitchWarnDialog::delayedInit()
{
FullScreenDialog::delayedInit();
lv_label_set_long_mode(messageLabel->getLvObj(), LV_LABEL_LONG_WRAP);
AUDIO_ERROR_MESSAGE(AU_SWITCH_ALERT);
}
@@ -103,6 +104,7 @@ ThrottleWarnDialog::ThrottleWarnDialog(const char* msg) :
void ThrottleWarnDialog::delayedInit()
{
FullScreenDialog::delayedInit();
lv_label_set_long_mode(messageLabel->getLvObj(), LV_LABEL_LONG_WRAP);
AUDIO_ERROR_MESSAGE(AU_THROTTLE_ALERT);
}
@@ -39,7 +39,7 @@ class SwitchWarnDialog : public FullScreenDialog
uint16_t last_bad_pots;
bool warningInactive();
void delayedInit() override;
void checkEvents() override;
@@ -56,6 +56,6 @@ class ThrottleWarnDialog : public FullScreenDialog
protected:
bool warningInactive();
void delayedInit() override;
};
@@ -49,11 +49,14 @@ FullScreenDialog::FullScreenDialog(
bringToTop();
build();
delayLoad();
}
void FullScreenDialog::delayedInit()
{
build();
}
void FullScreenDialog::build()
{
auto div = new Window(this, {0, ALERT_FRAME_TOP, LCD_W, ALERT_FRAME_HEIGHT});
@@ -27,63 +27,65 @@ class StaticText;
class FullScreenDialog : public Window
{
public:
FullScreenDialog(uint8_t type, std::string title, std::string message = "",
std::string action = "",
const std::function<void(void)>& confirmHandler = nullptr);
public:
FullScreenDialog(uint8_t type, std::string title, std::string message = "",
std::string action = "",
const std::function<void(void)>& confirmHandler = nullptr);
#if defined(DEBUG_WINDOWS)
std::string getName() const override
{
return "FullScreenDialog";
}
std::string getName() const override
{
return "FullScreenDialog";
}
#endif
void setMessage(std::string text);
void setMessage(std::string text);
void onEvent(event_t event) override;
void onCancel() override;
bool onLongPress() override;
void onEvent(event_t event) override;
void onCancel() override;
bool onLongPress() override;
void deleteLater(bool detach = true, bool trash = true) override;
void deleteLater(bool detach = true, bool trash = true) override;
void checkEvents() override;
void checkEvents() override;
void setCloseCondition(std::function<bool(void)> handler)
{
closeCondition = std::move(handler);
}
void setCloseCondition(std::function<bool(void)> handler)
{
closeCondition = std::move(handler);
}
void runForever(bool checkPwr = true);
void runForever(bool checkPwr = true);
void closeDialog();
void closeDialog();
static LAYOUT_SIZE_SCALED(ALERT_FRAME_TOP, 50, 70)
static LAYOUT_SIZE(ALERT_FRAME_HO, LAYOUT_SCALE(120), 2 * ALERT_FRAME_TOP)
static constexpr coord_t ALERT_FRAME_HEIGHT = LCD_H - ALERT_FRAME_HO;
static LAYOUT_SIZE_SCALED(ALERT_BMO, 25, 15)
static constexpr coord_t ALERT_BITMAP_TOP = ALERT_FRAME_TOP + ALERT_BMO;
static LAYOUT_SIZE_SCALED(ALERT_BITMAP_LEFT, 20, 15)
static LAYOUT_SIZE_SCALED(ALERT_TO, 5, 10)
static constexpr coord_t ALERT_TITLE_TOP = ALERT_FRAME_TOP + ALERT_TO;
static LAYOUT_SIZE_SCALED(ALERT_TITLE_LEFT, 146, 140)
static LAYOUT_SIZE_SCALED(ALERT_MO, 85, 130)
static constexpr coord_t ALERT_MESSAGE_TOP = ALERT_TITLE_TOP + ALERT_MO;
static LAYOUT_SIZE_SCALED(ALERT_MESSAGE_LEFT, 146, 15)
static LAYOUT_VAL_SCALED(ONEBTN_W, 280)
static LAYOUT_VAL_SCALED(ONEBTN_H, 40)
static LAYOUT_VAL_SCALED(TWOBTN_W, 100)
static LAYOUT_VAL_SCALED(TWOBTN_H, 40)
static LAYOUT_SIZE_SCALED(ALERT_FRAME_TOP, 50, 70)
static LAYOUT_SIZE(ALERT_FRAME_HO, LAYOUT_SCALE(120), 2 * ALERT_FRAME_TOP)
static constexpr coord_t ALERT_FRAME_HEIGHT = LCD_H - ALERT_FRAME_HO;
static LAYOUT_SIZE_SCALED(ALERT_BMO, 25, 15)
static constexpr coord_t ALERT_BITMAP_TOP = ALERT_FRAME_TOP + ALERT_BMO;
static LAYOUT_SIZE_SCALED(ALERT_BITMAP_LEFT, 20, 15)
static LAYOUT_SIZE_SCALED(ALERT_TO, 5, 10)
static constexpr coord_t ALERT_TITLE_TOP = ALERT_FRAME_TOP + ALERT_TO;
static LAYOUT_SIZE_SCALED(ALERT_TITLE_LEFT, 146, 140)
static LAYOUT_SIZE_SCALED(ALERT_MO, 85, 130)
static constexpr coord_t ALERT_MESSAGE_TOP = ALERT_TITLE_TOP + ALERT_MO;
static LAYOUT_SIZE_SCALED(ALERT_MESSAGE_LEFT, 146, 15)
static LAYOUT_VAL_SCALED(ONEBTN_W, 280)
static LAYOUT_VAL_SCALED(ONEBTN_H, 40)
static LAYOUT_VAL_SCALED(TWOBTN_W, 100)
static LAYOUT_VAL_SCALED(TWOBTN_H, 40)
protected:
uint8_t type;
std::string title;
std::string message;
std::string action;
bool running = false;
std::function<bool(void)> closeCondition;
std::function<void(void)> confirmHandler;
StaticText* messageLabel;
protected:
uint8_t type;
std::string title;
std::string message;
std::string action;
bool running = false;
std::function<bool(void)> closeCondition;
std::function<void(void)> confirmHandler;
StaticText* messageLabel;
void build();
void build();
void delayedInit() override;
};
+231 -180
View File
@@ -27,7 +27,7 @@
#include "fullscreen_dialog.h"
#include "lib_file.h"
// Used on startup to block until checlist is closed.
// Used on startup to block until checklist is closed.
static bool checkListOpen = false;
// Check Box
@@ -67,198 +67,236 @@ static lv_obj_t* checkbox_create(lv_obj_t* parent)
constexpr int maxTxtBuffSize = 64 * 1024;
class TextViewer
{
public:
TextViewer(const std::string path, const std::string name) : name(std::move(name))
{
fullPath = path + std::string(PATH_SEPARATOR) + name;
extractNameSansExt();
}
~TextViewer()
{
if (buffer) {
free(buffer);
buffer = nullptr;
}
}
void onEvent(event_t event)
{
#if defined(HARDWARE_KEYS)
if (int(bufSize) < fileLength) {
if (event == EVT_KEY_BREAK(KEY_PAGEDN)) {
offset += bufSize;
}
if (event == EVT_KEY_BREAK(KEY_PAGEUP)) {
offset -= bufSize;
}
offset = std::max(offset, 0);
offset = std::min(offset, fileLength - (int)bufSize);
sdReadTextFileBlock(bufSize, offset);
lv_label_set_text_static(lb, buffer);
}
#endif
}
void build(Window* window)
{
if (openFile()) {
auto obj = window->getLvObj();
lv_obj_add_flag(
obj, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_WITH_ARROW |
LV_OBJ_FLAG_SCROLL_MOMENTUM | LV_OBJ_FLAG_CLICK_FOCUSABLE);
etx_scrollbar(obj);
// prevents resetting the group's edit mode
lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
auto g = lv_group_get_default();
lb = lv_label_create(obj);
lv_obj_set_size(lb, lv_pct(100), LV_SIZE_CONTENT);
etx_obj_add_style(lb, styles->pad_medium, LV_PART_MAIN);
lv_group_add_obj(g, obj);
lv_group_set_editing(g, true);
lv_label_set_text_static(lb, buffer);
if (openFromEnd)
lv_obj_scroll_to_y(obj, LV_COORD_MAX, LV_ANIM_OFF);
else
lv_obj_scroll_to_y(obj, 0, LV_ANIM_OFF);
}
}
protected:
std::string name;
std::string fullPath;
lv_obj_t* lb;
int offset = 0;
char* buffer = nullptr;
size_t bufSize = 0;
int fileLength = 0;
bool openFromEnd;
void extractNameSansExt()
{
uint8_t nameLength;
uint8_t extLength;
const char* ext =
getFileExtension(name.c_str(), 0, 0, &nameLength, &extLength);
openFromEnd = !strcmp(ext, LOGS_EXT);
}
bool openFile()
{
FILINFO info;
if (buffer) {
free(buffer);
buffer = nullptr;
bufSize = 0;
}
auto res = f_stat((TCHAR*)fullPath.c_str(), &info);
if (res == FR_OK) {
fileLength = int(info.fsize);
bufSize = std::min(fileLength, maxTxtBuffSize) + 1;
buffer = (char*)malloc(bufSize);
if (buffer) {
offset = std::max(int(openFromEnd ? int(info.fsize) - bufSize + 1 : 0), 0);
if (sdReadTextFileBlock(bufSize, offset) == FR_OK) {
return true;
}
}
}
return false;
}
FRESULT sdReadTextFileBlock(const uint32_t bufSize, const uint32_t offset)
{
FIL file;
char escape_chars[4];
int escape = 0;
auto res =
f_open(&file, (TCHAR*)fullPath.c_str(), FA_OPEN_EXISTING | FA_READ);
if (res == FR_OK) {
res = f_lseek(&file, offset);
if (res == FR_OK) {
UINT br;
char c;
char* ptr = buffer;
for (int i = 0; i < (int)bufSize; i++) {
res = f_read(&file, &c, 1, &br);
if (res == FR_OK && br == 1) {
if (c == '\\' && escape == 0) {
escape = 1;
continue;
} else if (c != '\\' && escape > 0 &&
escape < (int)sizeof(escape_chars)) {
escape_chars[escape - 1] = c;
if (escape == 2 && !strncmp(escape_chars, "up", 2)) {
*ptr++ = STR_CHAR_UP[0];
c = STR_CHAR_UP[1];
escape = 0;
} else if (escape == 2 && !strncmp(escape_chars, "dn", 2)) {
*ptr++ = STR_CHAR_DOWN[0];
c = STR_CHAR_DOWN[1];
escape = 0;
} else if (escape == 3) {
int val = atoi(escape_chars);
if (val >= 200 && val < 225) {
*ptr++ = '\302';
c = '\200' + val - 200;
}
} else if (escape == 1 && c == '~') {
c = 'z' + 1;
} else {
escape++;
continue;
}
} else if (c == '\t') {
c = 0x1D; // tab
}
escape = 0;
if (c == 0xA && *(ptr - 1) == 0xD) {
*(ptr - 1) = '\n';
continue;
}
*ptr++ = c;
}
}
*ptr = '\0';
}
f_close(&file);
}
return res;
}
};
ViewTextWindow::ViewTextWindow(const std::string path, const std::string name,
EdgeTxIcon icon) :
Page(icon, PAD_ZERO), path(std::move(path)), name(std::move(name))
Page(icon, PAD_ZERO)
{
fullPath = this->path + std::string(PATH_SEPARATOR) + this->name;
extractNameSansExt();
textViewer = new TextViewer(path, name);
header->setTitle(this->name);
header->setTitle(name);
delayLoad();
};
void ViewTextWindow::delayedInit()
{
if (buffer == nullptr) buildBody(body);
textViewer->build(body);
}
void ViewTextWindow::onCancel()
{
if (textViewer) delete textViewer;
Page::onCancel();
checkListOpen = false;
}
void ViewTextWindow::extractNameSansExt()
{
uint8_t nameLength;
uint8_t extLength;
const char* ext =
getFileExtension(name.c_str(), 0, 0, &nameLength, &extLength);
extension = std::string(ext);
openFromEnd = !strcmp(ext, LOGS_EXT);
}
bool ViewTextWindow::openFile()
{
FILINFO info;
if (buffer) {
free(buffer);
buffer = nullptr;
bufSize = 0;
}
auto res = f_stat((TCHAR*)fullPath.c_str(), &info);
if (res == FR_OK) {
fileLength = int(info.fsize);
bufSize = std::min(fileLength, maxTxtBuffSize) + 1;
buffer = (char*)malloc(bufSize);
if (buffer) {
offset =
std::max(int(openFromEnd ? int(info.fsize) - bufSize + 1 : 0), 0);
TRACE("info.fsize=%d\tbufSize=%d\toffset=%d", info.fsize, bufSize,
int(info.fsize) - bufSize + 1);
if (sdReadTextFileBlock(bufSize, offset) == FR_OK) {
return true;
}
}
}
return false;
}
void ViewTextWindow::buildBody(Window* window)
{
if (openFile()) {
auto obj = window->getLvObj();
lv_obj_add_flag(
obj, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_SCROLL_WITH_ARROW |
LV_OBJ_FLAG_SCROLL_MOMENTUM | LV_OBJ_FLAG_CLICK_FOCUSABLE);
etx_scrollbar(obj);
// prevents resetting the group's edit mode
lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
auto g = lv_group_get_default();
lb = lv_label_create(obj);
lv_obj_set_size(lb, lv_pct(100), LV_SIZE_CONTENT);
etx_obj_add_style(lb, styles->pad_medium, LV_PART_MAIN);
lv_group_add_obj(g, obj);
lv_group_set_editing(g, true);
lv_label_set_text_static(lb, buffer);
if (openFromEnd)
lv_obj_scroll_to_y(obj, LV_COORD_MAX, LV_ANIM_OFF);
else
lv_obj_scroll_to_y(obj, 0, LV_ANIM_OFF);
}
}
FRESULT ViewTextWindow::sdReadTextFileBlock(const uint32_t bufSize,
const uint32_t offset)
{
FIL file;
char escape_chars[4];
int escape = 0;
auto res =
f_open(&file, (TCHAR*)fullPath.c_str(), FA_OPEN_EXISTING | FA_READ);
if (res == FR_OK) {
res = f_lseek(&file, offset);
if (res == FR_OK) {
UINT br;
char c;
char* ptr = buffer;
for (int i = 0; i < (int)bufSize; i++) {
res = f_read(&file, &c, 1, &br);
if (res == FR_OK && br == 1) {
if (c == '\\' && escape == 0) {
escape = 1;
continue;
} else if (c != '\\' && escape > 0 &&
escape < (int)sizeof(escape_chars)) {
escape_chars[escape - 1] = c;
if (escape == 2 && !strncmp(escape_chars, "up", 2)) {
*ptr++ = STR_CHAR_UP[0];
c = STR_CHAR_UP[1];
escape = 0;
} else if (escape == 2 && !strncmp(escape_chars, "dn", 2)) {
*ptr++ = STR_CHAR_DOWN[0];
c = STR_CHAR_DOWN[1];
escape = 0;
} else if (escape == 3) {
int val = atoi(escape_chars);
if (val >= 200 && val < 225) {
*ptr++ = '\302';
c = '\200' + val - 200;
}
} else if (escape == 1 && c == '~') {
c = 'z' + 1;
} else {
escape++;
continue;
}
} else if (c == '\t') {
c = 0x1D; // tab
}
escape = 0;
if (c == 0xA && *(ptr - 1) == 0xD) {
*(ptr - 1) = '\n';
continue;
}
*ptr++ = c;
}
}
*ptr = '\0';
}
f_close(&file);
}
return res;
}
void ViewTextWindow::onEvent(event_t event)
{
#if defined(HARDWARE_KEYS)
if (int(bufSize) < fileLength) {
TRACE("BEFORE offset=%d", offset);
if (event == EVT_KEY_BREAK(KEY_PAGEDN)) {
offset += bufSize;
TRACE("event=DOWN");
}
if (event == EVT_KEY_BREAK(KEY_PAGEUP)) {
TRACE("event=UP");
offset -= bufSize;
}
offset = std::max(offset, 0);
offset = std::min(offset, fileLength - (int)bufSize);
TRACE("AFTER offset=%d", offset);
sdReadTextFileBlock(bufSize, offset);
lv_label_set_text_static(lb, buffer);
}
if (textViewer) textViewer->onEvent(event);
if (event == EVT_KEY_BREAK(KEY_EXIT)) onCancel();
#endif
}
class ViewChecklistWindow : public ViewTextWindow
class ViewChecklistWindow : public Page, public TextViewer
{
public:
ViewChecklistWindow(const std::string path, const std::string name,
EdgeTxIcon icon) :
ViewTextWindow(path, name, icon)
Page(icon, PAD_ZERO), TextViewer(path, name)
{
header->setTitle(g_model.header.name);
header->setTitle2(STR_PREFLIGHT);
fullPath = path + std::string(PATH_SEPARATOR) + name;
extractNameSansExt();
delayLoad();
}
void delayedInit() override
{
if (buffer == nullptr) buildBody(body);
}
#if defined(DEBUG_WINDOWS)
@@ -267,7 +305,10 @@ class ViewChecklistWindow : public ViewTextWindow
void onCancel() override
{
if (allChecked()) ViewTextWindow::onCancel();
if (allChecked()) {
Page::onCancel();
checkListOpen = false;
}
}
protected:
@@ -330,7 +371,7 @@ class ViewChecklistWindow : public ViewTextWindow
if (vtw) vtw->updateCheckboxes();
}
void buildBody(Window* window) override
void buildBody(Window* window)
{
if (openFile()) {
auto obj = window->getLvObj();
@@ -456,27 +497,19 @@ static std::string getModelNotesFile()
return std::string("");
}
static bool openNotes(std::string modelNotesName,
bool fromMenu = false)
{
std::string fullPath = std::string(MODELS_PATH) + PATH_SEPARATOR + modelNotesName;
if (isFileAvailable(fullPath.c_str())) {
if (fromMenu || !g_model.checklistInteractive)
new ViewTextWindow(std::string(MODELS_PATH), modelNotesName, ICON_MODEL);
else
new ViewChecklistWindow(std::string(MODELS_PATH), modelNotesName, ICON_MODEL);
return true;
} else {
return false;
}
}
void readModelNotes(bool fromMenu)
{
std::string modelNotesName = getModelNotesFile();
if (!modelNotesName.empty()) {
openNotes(modelNotesName, fromMenu);
std::string fullPath = std::string(MODELS_PATH) + PATH_SEPARATOR + modelNotesName;
if (isFileAvailable(fullPath.c_str())) {
if (fromMenu || !g_model.checklistInteractive)
new ViewTextWindow(std::string(MODELS_PATH), modelNotesName, ICON_MODEL);
else
new ViewChecklistWindow(std::string(MODELS_PATH), modelNotesName, ICON_MODEL);
} else {
}
}
}
@@ -500,8 +533,10 @@ class CheckListDialog : public FullScreenDialog
bool warningInactive()
{
if (!checkListOpen)
if (!checkListOpen) {
LED_ERROR_END();
deleteLater();
}
return !checkListOpen;
}
};
@@ -515,3 +550,19 @@ void readChecklist()
dialog->runForever();
}
}
ModelNotesPage::ModelNotesPage(PageDef& pageDef) : PageGroupItem(pageDef, PAD_ZERO)
{
}
void ModelNotesPage::build(Window* window)
{
if (!textViewer) textViewer = new TextViewer(MODELS_PATH, getModelNotesFile());
if (textViewer) textViewer->build(window);
}
void ModelNotesPage::cleanup()
{
if (textViewer) delete textViewer;
textViewer = nullptr;
}
+21 -27
View File
@@ -20,8 +20,11 @@
#pragma once
#include "ff.h"
#include "page.h"
#include "pagegroup.h"
#include "gui_common.h"
class TextViewer;
class ViewTextWindow : public Page
{
@@ -32,42 +35,33 @@ class ViewTextWindow : public Page
FRESULT sdReadTextFileBlock(const uint32_t bufSize,
const uint32_t offset);
~ViewTextWindow()
{
if (buffer) {
free(buffer);
buffer = nullptr;
}
}
void onCancel() override;
#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "ViewTextWindow"; };
#endif
void delayedInit() override;
protected:
std::string path;
std::string name;
std::string fullPath;
std::string extension;
lv_obj_t* lb;
int offset = 0;
char* buffer = nullptr;
size_t bufSize = 0;
int fileLength = 0;
bool openFromEnd;
void extractNameSansExt(void);
virtual void buildBody(Window* window);
bool openFile();
TextViewer* textViewer = nullptr;
void onEvent(event_t event) override;
};
void delayedInit() override;
class ModelNotesPage : public PageGroupItem
{
public:
ModelNotesPage(PageDef& pageDef);
bool isVisible() const override { return modelHasNotes(); }
void build(Window* window) override;
void cleanup() override;
protected:
TextViewer* textViewer = nullptr;
};
void readModelNotes(bool fromMenu = false);
@@ -33,6 +33,7 @@
#include "model_setup.h"
#include "model_telemetry.h"
#include "special_functions.h"
#include "view_text.h"
PageDef modelMenuItems[] = {
{ ICON_MODEL_SETUP, STR_QM_GEN_SETTINGS, STR_MAIN_MENU_SETTINGS, PAGE_CREATE, QuickMenu::MODEL_SETUP, [](PageDef& pageDef) { return new ModelSetupPage(pageDef); }},
@@ -52,5 +53,6 @@ PageDef modelMenuItems[] = {
{ ICON_MODEL_LUA_SCRIPTS, STR_QM_CUSTOM_LUA, STR_MENUCUSTOMSCRIPTS, PAGE_CREATE, QuickMenu::MODEL_SCRIPTS, [](PageDef& pageDef) { return new ModelMixerScriptsPage(pageDef); }, modelCustomScriptsEnabled},
#endif
{ ICON_MODEL_TELEMETRY, STR_QM_TELEM, STR_MENUTELEMETRY, PAGE_CREATE, QuickMenu::MODEL_TELEMETRY, [](PageDef& pageDef) { return new ModelTelemetryPage(pageDef); }, modelTelemetryEnabled},
{ ICON_MODEL_NOTES, STR_MAIN_MENU_MODEL_NOTES, STR_MAIN_MENU_MODEL_NOTES, PAGE_CREATE, QuickMenu::MODEL_NOTES, [](PageDef& pageDef) { return new ModelNotesPage(pageDef); }, modelHasNotes},
{ EDGETX_ICONS_COUNT }
};
@@ -25,7 +25,6 @@
#include "edgetx.h"
#include "quick_menu_group.h"
#include "etx_lv_theme.h"
#include "view_text.h"
#include "view_main.h"
#include "screen_setup.h"
#include "theme_manager.h"
@@ -245,16 +244,6 @@ QuickMenu::QuickMenu() :
sub = new QuickSubMenu(box, this, ICON_RADIO_TOOLS, STR_QM_TOOLS, STR_QM_TOOLS, toolsMenuItems);
sub->addButton();
subMenus.emplace_back(sub);
mainMenu->addButton(ICON_MODEL_NOTES, STR_MAIN_MENU_MODEL_NOTES,
[=]() -> uint8_t {
onSelect(true);
readModelNotes(true);
return 0;
},
[=]() -> bool {
return modelHasNotes();
});
}
void QuickMenu::deleteLater(bool detach, bool trash)
@@ -288,7 +277,7 @@ void QuickMenu::openQM(std::function<void()> cancelHandler,
setFocus(curPage);
} else {
pageGroup = nullptr;
if (curPage > QuickMenu::MANAGE_MODELS) {
if (curPage >= QuickMenu::FIRST_SUB_MENU_ITEM) {
mainMenu->setDisabled(false);
mainMenu->clearFocus();
setFocus(curPage);
@@ -45,8 +45,9 @@ class QuickMenu : public NavWindow
enum SubMenu {
NONE = 0,
MANAGE_MODELS,
FIRST_SUB_MENU_ITEM,
// Model menu
MODEL_SETUP,
MODEL_SETUP = FIRST_SUB_MENU_ITEM,
MODEL_HELI,
MODEL_FLIGHTMODES,
MODEL_INPUTS,
@@ -58,6 +59,7 @@ class QuickMenu : public NavWindow
MODEL_SF,
MODEL_SCRIPTS,
MODEL_TELEMETRY,
MODEL_NOTES,
// Radio menu
RADIO_SETUP,
RADIO_GF,