Compare commits

...

2 Commits

Author SHA1 Message Date
Jason P
66bdf331dd Fix menu name from Position to GPS 2026-01-02 10:24:20 -06:00
Jason P
54d6509118 Refactored some of the system menus to the new DRY method 2026-01-02 10:07:46 -06:00
2 changed files with 487 additions and 361 deletions

View File

@@ -107,50 +107,60 @@ void menuHandler::OnboardMessage()
void menuHandler::LoraRegionPicker(uint32_t duration) void menuHandler::LoraRegionPicker(uint32_t duration)
{ {
static const char *optionsArray[] = {"Back", static const LoraRegionOption regionOptions[] = {
"US", {"Back", OptionsAction::Back},
"EU_433", {"US", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_US},
"EU_868", {"EU_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_433},
"CN", {"EU_868", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_868},
"JP", {"CN", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_CN},
"ANZ", {"JP", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_JP},
"KR", {"ANZ", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ANZ},
"TW", {"KR", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_KR},
"RU", {"TW", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_TW},
"IN", {"RU", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_RU},
"NZ_865", {"IN", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_IN},
"TH", {"NZ_865", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_NZ_865},
"LORA_24", {"TH", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_TH},
"UA_433", {"LORA_24", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_LORA_24},
"UA_868", {"UA_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_UA_433},
"MY_433", {"UA_868", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_UA_868},
"MY_" {"MY_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_MY_433},
"919", {"MY_919", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_MY_919},
"SG_" {"SG_923", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_SG_923},
"923", {"PH_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_PH_433},
"PH_433", {"PH_868", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_PH_868},
"PH_868", {"PH_915", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_PH_915},
"PH_915", {"ANZ_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ANZ_433},
"ANZ_433", {"KZ_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_KZ_433},
"KZ_433", {"KZ_863", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_KZ_863},
"KZ_863", {"NP_865", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_NP_865},
"NP_865", {"BR_902", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_BR_902},
"BR_902"}; };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Set the LoRa region"; constexpr size_t regionCount = sizeof(regionOptions) / sizeof(regionOptions[0]);
static std::array<const char *, regionCount> regionLabels{};
const char *bannerMessage = "Set the LoRa region";
if (currentResolution == ScreenResolution::UltraLow) { if (currentResolution == ScreenResolution::UltraLow) {
bannerOptions.message = "LoRa Region"; bannerMessage = "LoRa Region";
} }
bannerOptions.durationMs = duration;
bannerOptions.optionsArrayPtr = optionsArray; auto bannerOptions =
bannerOptions.optionsCount = 27; createStaticBannerOptions(bannerMessage, regionOptions, regionLabels, [](const LoraRegionOption &option, int) -> void {
bannerOptions.InitialSelected = 0; if (!option.hasValue) {
bannerOptions.bannerCallback = [](int selected) -> void { return;
if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) { }
config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected);
auto selectedRegion = option.value;
if (config.lora.region == selectedRegion) {
return;
}
config.lora.region = selectedRegion;
auto changes = SEGMENT_CONFIG; auto changes = SEGMENT_CONFIG;
// This is needed as we wait til picking the LoRa region to generate keys for the first time. // FIXME: This should be a method consolidated with the same logic in the admin message as well
// This is needed as we wait til picking the LoRa region to generate keys for the first time.
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
if (!owner.is_licensed) { if (!owner.is_licensed) {
bool keygenSuccess = false; bool keygenSuccess = false;
@@ -187,8 +197,19 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
service->reloadConfig(changes); service->reloadConfig(changes);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
});
bannerOptions.durationMs = duration;
int initialSelection = 0;
for (size_t i = 0; i < regionCount; ++i) {
if (regionOptions[i].hasValue && regionOptions[i].value == config.lora.region) {
initialSelection = static_cast<int>(i);
break;
} }
}; }
bannerOptions.InitialSelected = initialSelection;
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
@@ -303,102 +324,100 @@ void menuHandler::showConfirmationBanner(const char *message, std::function<void
void menuHandler::ClockFacePicker() void menuHandler::ClockFacePicker()
{ {
static const char *optionsArray[] = {"Back", "Digital", "Analog"}; static const ClockFaceOption clockFaceOptions[] = {
enum optionsNumbers { Back = 0, Digital = 1, Analog = 2 }; {"Back", OptionsAction::Back},
BannerOverlayOptions bannerOptions; {"Digital", OptionsAction::Select, false},
bannerOptions.message = "Which Face?"; {"Analog", OptionsAction::Select, true},
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
menuHandler::menuQueue = menuHandler::clock_menu;
screen->runNow();
} else if (selected == Digital) {
uiconfig.is_clockface_analog = false;
saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK);
} else {
uiconfig.is_clockface_analog = true;
saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK);
}
}; };
constexpr size_t clockFaceCount = sizeof(clockFaceOptions) / sizeof(clockFaceOptions[0]);
static std::array<const char *, clockFaceCount> clockFaceLabels{};
auto bannerOptions = createStaticBannerOptions("Which Face?", clockFaceOptions, clockFaceLabels,
[](const ClockFaceOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuHandler::menuQueue = menuHandler::clock_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
if (uiconfig.is_clockface_analog == option.value) {
return;
}
uiconfig.is_clockface_analog = option.value;
saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK);
});
bannerOptions.InitialSelected = uiconfig.is_clockface_analog ? 2 : 1; bannerOptions.InitialSelected = uiconfig.is_clockface_analog ? 2 : 1;
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::TZPicker() void menuHandler::TZPicker()
{ {
static const char *optionsArray[] = {"Back", static const TimezoneOption timezoneOptions[] = {
"US/Hawaii", {"Back", OptionsAction::Back},
"US/Alaska", {"US/Hawaii", OptionsAction::Select, "HST10"},
"US/Pacific", {"US/Alaska", OptionsAction::Select, "AKST9AKDT,M3.2.0,M11.1.0"},
"US/Arizona", {"US/Pacific", OptionsAction::Select, "PST8PDT,M3.2.0,M11.1.0"},
"US/Mountain", {"US/Arizona", OptionsAction::Select, "MST7"},
"US/Central", {"US/Mountain", OptionsAction::Select, "MST7MDT,M3.2.0,M11.1.0"},
"US/Eastern", {"US/Central", OptionsAction::Select, "CST6CDT,M3.2.0,M11.1.0"},
"BR/Brasilia", {"US/Eastern", OptionsAction::Select, "EST5EDT,M3.2.0,M11.1.0"},
"UTC", {"BR/Brasilia", OptionsAction::Select, "BRT3"},
"EU/Western", {"UTC", OptionsAction::Select, "UTC0"},
"EU/" {"EU/Western", OptionsAction::Select, "GMT0BST,M3.5.0/1,M10.5.0"},
"Central", {"EU/Central", OptionsAction::Select, "CET-1CEST,M3.5.0,M10.5.0/3"},
"EU/Eastern", {"EU/Eastern", OptionsAction::Select, "EET-2EEST,M3.5.0/3,M10.5.0/4"},
"Asia/Kolkata", {"Asia/Kolkata", OptionsAction::Select, "IST-5:30"},
"Asia/Hong_Kong", {"Asia/Hong_Kong", OptionsAction::Select, "HKT-8"},
"AU/AWST", {"AU/AWST", OptionsAction::Select, "AWST-8"},
"AU/ACST", {"AU/ACST", OptionsAction::Select, "ACST-9:30ACDT,M10.1.0,M4.1.0/3"},
"AU/AEST", {"AU/AEST", OptionsAction::Select, "AEST-10AEDT,M10.1.0,M4.1.0/3"},
"Pacific/NZ"}; {"Pacific/NZ", OptionsAction::Select, "NZST-12NZDT,M9.5.0,M4.1.0/3"},
BannerOverlayOptions bannerOptions; };
bannerOptions.message = "Pick Timezone";
bannerOptions.optionsArrayPtr = optionsArray; constexpr size_t timezoneCount = sizeof(timezoneOptions) / sizeof(timezoneOptions[0]);
bannerOptions.optionsCount = 19; static std::array<const char *, timezoneCount> timezoneLabels{};
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) { auto bannerOptions = createStaticBannerOptions(
menuHandler::menuQueue = menuHandler::clock_menu; "Pick Timezone", timezoneOptions, timezoneLabels, [](const TimezoneOption &option, int) -> void {
screen->runNow(); if (option.action == OptionsAction::Back) {
} else if (selected == 1) { // Hawaii menuHandler::menuQueue = menuHandler::clock_menu;
strncpy(config.device.tzdef, "HST10", sizeof(config.device.tzdef)); screen->runNow();
} else if (selected == 2) { // Alaska return;
strncpy(config.device.tzdef, "AKST9AKDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef)); }
} else if (selected == 3) { // Pacific
strncpy(config.device.tzdef, "PST8PDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef)); if (!option.hasValue) {
} else if (selected == 4) { // Arizona return;
strncpy(config.device.tzdef, "MST7", sizeof(config.device.tzdef)); }
} else if (selected == 5) { // Mountain
strncpy(config.device.tzdef, "MST7MDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef)); if (strncmp(config.device.tzdef, option.value, sizeof(config.device.tzdef)) == 0) {
} else if (selected == 6) { // Central return;
strncpy(config.device.tzdef, "CST6CDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef)); }
} else if (selected == 7) { // Eastern
strncpy(config.device.tzdef, "EST5EDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef)); strncpy(config.device.tzdef, option.value, sizeof(config.device.tzdef));
} else if (selected == 8) { // Brazil config.device.tzdef[sizeof(config.device.tzdef) - 1] = '\0';
strncpy(config.device.tzdef, "BRT3", sizeof(config.device.tzdef));
} else if (selected == 9) { // UTC
strncpy(config.device.tzdef, "UTC0", sizeof(config.device.tzdef));
} else if (selected == 10) { // EU/Western
strncpy(config.device.tzdef, "GMT0BST,M3.5.0/1,M10.5.0", sizeof(config.device.tzdef));
} else if (selected == 11) { // EU/Central
strncpy(config.device.tzdef, "CET-1CEST,M3.5.0,M10.5.0/3", sizeof(config.device.tzdef));
} else if (selected == 12) { // EU/Eastern
strncpy(config.device.tzdef, "EET-2EEST,M3.5.0/3,M10.5.0/4", sizeof(config.device.tzdef));
} else if (selected == 13) { // Asia/Kolkata
strncpy(config.device.tzdef, "IST-5:30", sizeof(config.device.tzdef));
} else if (selected == 14) { // China
strncpy(config.device.tzdef, "HKT-8", sizeof(config.device.tzdef));
} else if (selected == 15) { // AU/AWST
strncpy(config.device.tzdef, "AWST-8", sizeof(config.device.tzdef));
} else if (selected == 16) { // AU/ACST
strncpy(config.device.tzdef, "ACST-9:30ACDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef));
} else if (selected == 17) { // AU/AEST
strncpy(config.device.tzdef, "AEST-10AEDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef));
} else if (selected == 18) { // NZ
strncpy(config.device.tzdef, "NZST-12NZDT,M9.5.0,M4.1.0/3", sizeof(config.device.tzdef));
}
if (selected != 0) {
setenv("TZ", config.device.tzdef, 1); setenv("TZ", config.device.tzdef, 1);
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
});
int initialSelection = 0;
for (size_t i = 0; i < timezoneCount; ++i) {
if (timezoneOptions[i].hasValue &&
strncmp(config.device.tzdef, timezoneOptions[i].value, sizeof(config.device.tzdef)) == 0) {
initialSelection = static_cast<int>(i);
break;
} }
}; }
bannerOptions.InitialSelected = initialSelection;
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
@@ -458,10 +477,9 @@ void menuHandler::messageResponseMenu()
#endif #endif
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action";
if (currentResolution == ScreenResolution::UltraLow) { if (currentResolution == ScreenResolution::UltraLow) {
bannerOptions.message = "Message"; bannerOptions.message = "Message";
} else {
bannerOptions.message = "Message Action";
} }
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -910,8 +928,12 @@ void menuHandler::homeBaseMenu()
} else if (selected == Sleep) { } else if (selected == Sleep) {
screen->setOn(false); screen->setOn(false);
} else if (selected == Position) { } else if (selected == Position) {
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SEND_PING, .kbchar = 0, .touchX = 0, .touchY = 0}; service->refreshLocalMeshNode();
inputBroker->injectInputEvent(&event); if (service->trySendPosition(NODENUM_BROADCAST, true)) {
IF_SCREEN(screen->showSimpleBanner("Position\nSent", 3000));
} else {
IF_SCREEN(screen->showSimpleBanner("Node Info\nSent", 3000));
}
} else if (selected == Preset) { } else if (selected == Preset) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST); cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} else if (selected == Freetext) { } else if (selected == Freetext) {
@@ -1108,57 +1130,92 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu() void menuHandler::positionBaseMenu()
{ {
enum optionsNumbers { enum class PositionAction {
Back, GpsToggle,
GPSToggle, GpsFormat,
GPSFormat,
CompassMenu, CompassMenu,
CompassCalibrate, CompassCalibrate,
GPSSmartPosition, GPSSmartPosition,
GPSUpdateInterval, GPSUpdateInterval,
GPSPositionBroadcast, GPSPositionBroadcast
enumEnd
}; };
static const char *optionsArray[enumEnd] = { static const PositionMenuOption baseOptions[] = {
"Back", "On/Off Toggle", "Format", "Smart Position", "Update Interval", "Broadcast Interval", "Compass"}; {"Back", OptionsAction::Back},
static int optionsEnumArray[enumEnd] = { {"On/Off Toggle", OptionsAction::Select, static_cast<int>(PositionAction::GpsToggle)},
Back, GPSToggle, GPSFormat, GPSSmartPosition, GPSUpdateInterval, GPSPositionBroadcast, CompassMenu}; {"Format", OptionsAction::Select, static_cast<int>(PositionAction::GpsFormat)},
int options = 7; {"Smart Position", OptionsAction::Select, static_cast<int>(PositionAction::GPSSmartPosition)},
{"Update Interval", OptionsAction::Select, static_cast<int>(PositionAction::GPSUpdateInterval)},
{"Broadcast Interval", OptionsAction::Select, static_cast<int>(PositionAction::GPSPositionBroadcast)},
{"Compass", OptionsAction::Select, static_cast<int>(PositionAction::CompassMenu)},
};
if (accelerometerThread) { static const PositionMenuOption calibrateOptions[] = {
optionsArray[options] = "Compass Calibrate"; {"Back", OptionsAction::Back},
optionsEnumArray[options++] = CompassCalibrate; {"On/Off Toggle", OptionsAction::Select, static_cast<int>(PositionAction::GpsToggle)},
} {"Format", OptionsAction::Select, static_cast<int>(PositionAction::GpsFormat)},
{"Smart Position", OptionsAction::Select, static_cast<int>(PositionAction::GPSSmartPosition)},
{"Update Interval", OptionsAction::Select, static_cast<int>(PositionAction::GPSUpdateInterval)},
{"Broadcast Interval", OptionsAction::Select, static_cast<int>(PositionAction::GPSPositionBroadcast)},
{"Compass", OptionsAction::Select, static_cast<int>(PositionAction::CompassMenu)},
{"Compass Calibrate", OptionsAction::Select, static_cast<int>(PositionAction::CompassCalibrate)},
};
BannerOverlayOptions bannerOptions; constexpr size_t baseCount = sizeof(baseOptions) / sizeof(baseOptions[0]);
bannerOptions.message = "GPS Action"; constexpr size_t calibrateCount = sizeof(calibrateOptions) / sizeof(calibrateOptions[0]);
bannerOptions.optionsArrayPtr = optionsArray; static std::array<const char *, baseCount> baseLabels{};
bannerOptions.optionsEnumPtr = optionsEnumArray; static std::array<const char *, calibrateCount> calibrateLabels{};
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { auto onSelection = [](const PositionMenuOption &option, int) -> void {
if (selected == GPSToggle) { if (option.action == OptionsAction::Back) {
return;
}
if (!option.hasValue) {
return;
}
auto action = static_cast<PositionAction>(option.value);
switch (action) {
case PositionAction::GpsToggle:
menuQueue = gps_toggle_menu; menuQueue = gps_toggle_menu;
screen->runNow(); screen->runNow();
} else if (selected == GPSFormat) { break;
case PositionAction::GpsFormat:
menuQueue = gps_format_menu; menuQueue = gps_format_menu;
screen->runNow(); screen->runNow();
} else if (selected == CompassMenu) { break;
case PositionAction::CompassMenu:
menuQueue = compass_point_north_menu; menuQueue = compass_point_north_menu;
screen->runNow(); screen->runNow();
} else if (selected == CompassCalibrate) { break;
accelerometerThread->calibrate(30); case PositionAction::CompassCalibrate:
} else if (selected == GPSSmartPosition) { if (accelerometerThread) {
accelerometerThread->calibrate(30);
}
break;
case PositionAction::GPSSmartPosition:
menuQueue = gps_smart_position_menu; menuQueue = gps_smart_position_menu;
screen->runNow(); screen->runNow();
} else if (selected == GPSUpdateInterval) { break;
case PositionAction::GPSUpdateInterval:
menuQueue = gps_update_interval_menu; menuQueue = gps_update_interval_menu;
screen->runNow(); screen->runNow();
} else if (selected == GPSPositionBroadcast) { break;
case PositionAction::GPSPositionBroadcast:
menuQueue = gps_position_broadcast_menu; menuQueue = gps_position_broadcast_menu;
screen->runNow(); screen->runNow();
break;
} }
}; };
BannerOverlayOptions bannerOptions;
if (accelerometerThread) {
bannerOptions = createStaticBannerOptions("GPS Action", calibrateOptions, calibrateLabels, onSelection);
} else {
bannerOptions = createStaticBannerOptions("GPS Action", baseOptions, baseLabels, onSelection);
}
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
@@ -1214,27 +1271,38 @@ void menuHandler::nodeListMenu()
void menuHandler::nodeNameLengthMenu() void menuHandler::nodeNameLengthMenu()
{ {
enum OptionsNumbers { Back, Long, Short }; static const NodeNameOption nodeNameOptions[] = {
static const char *optionsArray[] = {"Back", "Long", "Short"}; {"Back", OptionsAction::Back},
BannerOverlayOptions bannerOptions; {"Long", OptionsAction::Select, true},
bannerOptions.message = "Node Name Length"; {"Short", OptionsAction::Select, false},
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Long) {
// Set names to long
LOG_INFO("Setting names to long");
config.display.use_long_node_name = true;
} else if (selected == Short) {
// Set names to short
LOG_INFO("Setting names to short");
config.display.use_long_node_name = false;
} else if (selected == Back) {
menuQueue = node_base_menu;
screen->runNow();
}
}; };
bannerOptions.InitialSelected = config.display.use_long_node_name == true ? 1 : 2;
constexpr size_t nodeNameCount = sizeof(nodeNameOptions) / sizeof(nodeNameOptions[0]);
static std::array<const char *, nodeNameCount> nodeNameLabels{};
auto bannerOptions = createStaticBannerOptions("Node Name Length", nodeNameOptions, nodeNameLabels,
[](const NodeNameOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuQueue = node_base_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
if (config.display.use_long_node_name == option.value) {
return;
}
config.display.use_long_node_name = option.value;
LOG_INFO("Setting names to %s", option.value ? "long" : "short");
});
int initialSelection = config.display.use_long_node_name ? 1 : 2;
bannerOptions.InitialSelected = initialSelection;
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
@@ -1268,119 +1336,169 @@ void menuHandler::resetNodeDBMenu()
void menuHandler::compassNorthMenu() void menuHandler::compassNorthMenu()
{ {
enum optionsNumbers { Back, Dynamic, Fixed, Freeze }; static const CompassOption compassOptions[] = {
static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"}; {"Back", OptionsAction::Back},
BannerOverlayOptions bannerOptions; {"Dynamic", OptionsAction::Select, meshtastic_CompassMode_DYNAMIC},
bannerOptions.message = "North Directions?"; {"Fixed Ring", OptionsAction::Select, meshtastic_CompassMode_FIXED_RING},
bannerOptions.optionsArrayPtr = optionsArray; {"Freeze Heading", OptionsAction::Select, meshtastic_CompassMode_FREEZE_HEADING},
bannerOptions.optionsCount = 4;
bannerOptions.InitialSelected = uiconfig.compass_mode + 1;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dynamic) {
if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) {
uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC;
saveUIConfig();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Fixed) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) {
uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING;
saveUIConfig();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Freeze) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING;
saveUIConfig();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Back) {
menuQueue = position_base_menu;
screen->runNow();
}
}; };
constexpr size_t compassCount = sizeof(compassOptions) / sizeof(compassOptions[0]);
static std::array<const char *, compassCount> compassLabels{};
auto bannerOptions = createStaticBannerOptions("North Directions?", compassOptions, compassLabels,
[](const CompassOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuQueue = position_base_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
if (uiconfig.compass_mode == option.value) {
return;
}
uiconfig.compass_mode = option.value;
saveUIConfig();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
});
int initialSelection = 0;
for (size_t i = 0; i < compassCount; ++i) {
if (compassOptions[i].hasValue && uiconfig.compass_mode == compassOptions[i].value) {
initialSelection = static_cast<int>(i);
break;
}
}
bannerOptions.InitialSelected = initialSelection;
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
void menuHandler::GPSToggleMenu() void menuHandler::GPSToggleMenu()
{ {
static const GPSToggleOption gpsToggleOptions[] = {
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; {"Back", OptionsAction::Back},
BannerOverlayOptions bannerOptions; {"Enabled", OptionsAction::Select, meshtastic_Config_PositionConfig_GpsMode_ENABLED},
bannerOptions.message = "Toggle GPS"; {"Disabled", OptionsAction::Select, meshtastic_Config_PositionConfig_GpsMode_DISABLED},
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
playGPSEnableBeep();
gps->enable();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 2) {
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED;
playGPSDisableBeep();
gps->disable();
service->reloadConfig(SEGMENT_CONFIG);
} else {
menuQueue = position_base_menu;
screen->runNow();
}
}; };
bannerOptions.InitialSelected = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED ? 1 : 2;
constexpr size_t toggleCount = sizeof(gpsToggleOptions) / sizeof(gpsToggleOptions[0]);
static std::array<const char *, toggleCount> toggleLabels{};
auto bannerOptions =
createStaticBannerOptions("Toggle GPS", gpsToggleOptions, toggleLabels, [](const GPSToggleOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuQueue = position_base_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
if (config.position.gps_mode == option.value) {
return;
}
config.position.gps_mode = option.value;
if (option.value == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
playGPSEnableBeep();
gps->enable();
} else {
playGPSDisableBeep();
gps->disable();
}
service->reloadConfig(SEGMENT_CONFIG);
});
int initialSelection = 0;
for (size_t i = 0; i < toggleCount; ++i) {
if (gpsToggleOptions[i].hasValue && config.position.gps_mode == gpsToggleOptions[i].value) {
initialSelection = static_cast<int>(i);
break;
}
}
bannerOptions.InitialSelected = initialSelection;
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::GPSFormatMenu() void menuHandler::GPSFormatMenu()
{ {
static const GPSFormatOption formatOptionsHigh[] = {
{"Back", OptionsAction::Back},
{"Decimal Degrees", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC},
{"Degrees Minutes Seconds", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS},
{"Universal Transverse Mercator", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM},
{"Military Grid Reference System", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS},
{"Open Location Code", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC},
{"Ordnance Survey Grid Ref", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR},
{"Maidenhead Locator", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS},
};
static const char *optionsArray[] = {"Back", static const GPSFormatOption formatOptionsLow[] = {
(currentResolution == ScreenResolution::High) ? "Decimal Degrees" : "DEC", {"Back", OptionsAction::Back},
(currentResolution == ScreenResolution::High) ? "Degrees Minutes Seconds" : "DMS", {"DEC", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC},
(currentResolution == ScreenResolution::High) ? "Universal Transverse Mercator" : "UTM", {"DMS", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS},
(currentResolution == ScreenResolution::High) ? "Military Grid Reference System" {"UTM", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM},
: "MGRS", {"MGRS", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS},
(currentResolution == ScreenResolution::High) ? "Open Location Code" : "OLC", {"OLC", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC},
(currentResolution == ScreenResolution::High) ? "Ordnance Survey Grid Ref" : "OSGR", {"OSGR", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR},
(currentResolution == ScreenResolution::High) ? "Maidenhead Locator" : "MLS"}; {"MLS", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS},
BannerOverlayOptions bannerOptions; };
bannerOptions.message = "GPS Format";
bannerOptions.optionsArrayPtr = optionsArray; constexpr size_t formatCount = sizeof(formatOptionsHigh) / sizeof(formatOptionsHigh[0]);
bannerOptions.optionsCount = 8; static std::array<const char *, formatCount> formatLabelsHigh{};
bannerOptions.bannerCallback = [](int selected) -> void { static std::array<const char *, formatCount> formatLabelsLow{};
if (selected == 1) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC; auto onSelection = [](const GPSFormatOption &option, int) -> void {
saveUIConfig(); if (option.action == OptionsAction::Back) {
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 2) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 3) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 4) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 5) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 6) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 7) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else {
menuQueue = position_base_menu; menuQueue = position_base_menu;
screen->runNow(); screen->runNow();
return;
} }
if (!option.hasValue) {
return;
}
if (uiconfig.gps_format == option.value) {
return;
}
uiconfig.gps_format = option.value;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
}; };
bannerOptions.InitialSelected = uiconfig.gps_format + 1;
BannerOverlayOptions bannerOptions;
int initialSelection = 0;
if (currentResolution == ScreenResolution::High) {
bannerOptions = createStaticBannerOptions("GPS Format", formatOptionsHigh, formatLabelsHigh, onSelection);
for (size_t i = 0; i < formatCount; ++i) {
if (formatOptionsHigh[i].hasValue && uiconfig.gps_format == formatOptionsHigh[i].value) {
initialSelection = static_cast<int>(i);
break;
}
}
} else {
bannerOptions = createStaticBannerOptions("GPS Format", formatOptionsLow, formatLabelsLow, onSelection);
for (size_t i = 0; i < formatCount; ++i) {
if (formatOptionsLow[i].hasValue && uiconfig.gps_format == formatOptionsLow[i].value) {
initialSelection = static_cast<int>(i);
break;
}
}
}
bannerOptions.InitialSelected = initialSelection;
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
@@ -1701,100 +1819,63 @@ void menuHandler::switchToMUIMenu()
void menuHandler::TFTColorPickerMenu(OLEDDisplay *display) void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
{ {
static const char *optionsArray[] = { static const ScreenColorOption colorOptions[] = {
"Back", "Default", "Meshtastic Green", "Yellow", "Red", "Orange", "Purple", "Blue", "Teal", "Cyan", "Ice", "Pink", {"Back", OptionsAction::Back},
"White", "Gray"}; {"Default", OptionsAction::Select, ScreenColor(0, 0, 0, true)},
BannerOverlayOptions bannerOptions; {"Meshtastic Green", OptionsAction::Select, ScreenColor(103, 234, 148)},
bannerOptions.message = "Select Screen Color"; {"Yellow", OptionsAction::Select, ScreenColor(255, 255, 128)},
bannerOptions.optionsArrayPtr = optionsArray; {"Red", OptionsAction::Select, ScreenColor(255, 64, 64)},
bannerOptions.optionsCount = 14; {"Orange", OptionsAction::Select, ScreenColor(255, 160, 20)},
bannerOptions.bannerCallback = [display](int selected) -> void { {"Purple", OptionsAction::Select, ScreenColor(204, 153, 255)},
{"Blue", OptionsAction::Select, ScreenColor(0, 0, 255)},
{"Teal", OptionsAction::Select, ScreenColor(16, 102, 102)},
{"Cyan", OptionsAction::Select, ScreenColor(0, 255, 255)},
{"Ice", OptionsAction::Select, ScreenColor(173, 216, 230)},
{"Pink", OptionsAction::Select, ScreenColor(255, 105, 180)},
{"White", OptionsAction::Select, ScreenColor(255, 255, 255)},
{"Gray", OptionsAction::Select, ScreenColor(128, 128, 128)},
};
constexpr size_t colorCount = sizeof(colorOptions) / sizeof(colorOptions[0]);
static std::array<const char *, colorCount> colorLabels{};
auto bannerOptions = createStaticBannerOptions(
"Select Screen Color", colorOptions, colorLabels, [display](const ScreenColorOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuQueue = system_base_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \ #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \
HAS_TFT || defined(HACKADAY_COMMUNICATOR) HAS_TFT || defined(HACKADAY_COMMUNICATOR)
uint8_t TFT_MESH_r = 0; const ScreenColor &color = option.value;
uint8_t TFT_MESH_g = 0; if (color.useVariant) {
uint8_t TFT_MESH_b = 0; LOG_INFO("Setting color to system default or defined variant");
if (selected == 1) { } else {
LOG_INFO("Setting color to system default or defined variant"); LOG_INFO("Setting color to %s", option.label);
// Given just before we set all these to zero, we will allow this to go through }
} else if (selected == 2) {
LOG_INFO("Setting color to Meshtastic Green"); uint8_t r = color.r;
TFT_MESH_r = 103; uint8_t g = color.g;
TFT_MESH_g = 234; uint8_t b = color.b;
TFT_MESH_b = 148;
} else if (selected == 3) {
LOG_INFO("Setting color to Yellow");
TFT_MESH_r = 255;
TFT_MESH_g = 255;
TFT_MESH_b = 128;
} else if (selected == 4) {
LOG_INFO("Setting color to Red");
TFT_MESH_r = 255;
TFT_MESH_g = 64;
TFT_MESH_b = 64;
} else if (selected == 5) {
LOG_INFO("Setting color to Orange");
TFT_MESH_r = 255;
TFT_MESH_g = 160;
TFT_MESH_b = 20;
} else if (selected == 6) {
LOG_INFO("Setting color to Purple");
TFT_MESH_r = 204;
TFT_MESH_g = 153;
TFT_MESH_b = 255;
} else if (selected == 7) {
LOG_INFO("Setting color to Blue");
TFT_MESH_r = 0;
TFT_MESH_g = 0;
TFT_MESH_b = 255;
} else if (selected == 8) {
LOG_INFO("Setting color to Teal");
TFT_MESH_r = 16;
TFT_MESH_g = 102;
TFT_MESH_b = 102;
} else if (selected == 9) {
LOG_INFO("Setting color to Cyan");
TFT_MESH_r = 0;
TFT_MESH_g = 255;
TFT_MESH_b = 255;
} else if (selected == 10) {
LOG_INFO("Setting color to Ice");
TFT_MESH_r = 173;
TFT_MESH_g = 216;
TFT_MESH_b = 230;
} else if (selected == 11) {
LOG_INFO("Setting color to Pink");
TFT_MESH_r = 255;
TFT_MESH_g = 105;
TFT_MESH_b = 180;
} else if (selected == 12) {
LOG_INFO("Setting color to White");
TFT_MESH_r = 255;
TFT_MESH_g = 255;
TFT_MESH_b = 255;
} else if (selected == 13) {
LOG_INFO("Setting color to Gray");
TFT_MESH_r = 128;
TFT_MESH_g = 128;
TFT_MESH_b = 128;
} else {
menuQueue = system_base_menu;
screen->runNow();
}
if (selected != 0) {
display->setColor(BLACK); display->setColor(BLACK);
display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
display->setColor(WHITE); display->setColor(WHITE);
if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) { if (color.useVariant || (r == 0 && g == 0 && b == 0)) {
#ifdef TFT_MESH_OVERRIDE #ifdef TFT_MESH_OVERRIDE
TFT_MESH = TFT_MESH_OVERRIDE; TFT_MESH = TFT_MESH_OVERRIDE;
#else #else
TFT_MESH = COLOR565(0x67, 0xEA, 0x94); TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
#endif #endif
} else { } else {
TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b); TFT_MESH = COLOR565(r, g, b);
} }
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
@@ -1802,16 +1883,40 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
#endif #endif
screen->setFrames(graphics::Screen::FOCUS_SYSTEM); screen->setFrames(graphics::Screen::FOCUS_SYSTEM);
if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) { if (color.useVariant || (r == 0 && g == 0 && b == 0)) {
uiconfig.screen_rgb_color = 0; uiconfig.screen_rgb_color = 0;
} else { } else {
uiconfig.screen_rgb_color = (TFT_MESH_r << 16) | (TFT_MESH_g << 8) | TFT_MESH_b; uiconfig.screen_rgb_color =
(static_cast<uint32_t>(r) << 16) | (static_cast<uint32_t>(g) << 8) | static_cast<uint32_t>(b);
} }
LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color); LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color);
saveUIConfig(); saveUIConfig();
}
#endif #endif
}; });
int initialSelection = 0;
if (uiconfig.screen_rgb_color == 0) {
initialSelection = 1;
} else {
uint32_t currentColor = uiconfig.screen_rgb_color;
for (size_t i = 0; i < colorCount; ++i) {
if (!colorOptions[i].hasValue) {
continue;
}
const ScreenColor &color = colorOptions[i].value;
if (color.useVariant) {
continue;
}
uint32_t encoded =
(static_cast<uint32_t>(color.r) << 16) | (static_cast<uint32_t>(color.g) << 8) | static_cast<uint32_t>(color.b);
if (encoded == currentColor) {
initialSelection = static_cast<int>(i);
break;
}
}
}
bannerOptions.InitialSelected = initialSelection;
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }

View File

@@ -128,7 +128,28 @@ template <typename T> struct MenuOption {
MenuOption(const char *labelIn, OptionsAction actionIn) : label(labelIn), action(actionIn), hasValue(false), value() {} MenuOption(const char *labelIn, OptionsAction actionIn) : label(labelIn), action(actionIn), hasValue(false), value() {}
}; };
struct ScreenColor {
uint8_t r;
uint8_t g;
uint8_t b;
bool useVariant;
ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false)
: r(rIn), g(gIn), b(bIn), useVariant(variantIn)
{
}
};
using RadioPresetOption = MenuOption<meshtastic_Config_LoRaConfig_ModemPreset>; using RadioPresetOption = MenuOption<meshtastic_Config_LoRaConfig_ModemPreset>;
using LoraRegionOption = MenuOption<meshtastic_Config_LoRaConfig_RegionCode>;
using TimezoneOption = MenuOption<const char *>;
using CompassOption = MenuOption<meshtastic_CompassMode>;
using ScreenColorOption = MenuOption<ScreenColor>;
using GPSToggleOption = MenuOption<meshtastic_Config_PositionConfig_GpsMode>;
using GPSFormatOption = MenuOption<meshtastic_DeviceUIConfig_GpsCoordinateFormat>;
using NodeNameOption = MenuOption<bool>;
using PositionMenuOption = MenuOption<int>;
using ClockFaceOption = MenuOption<bool>;
} // namespace graphics } // namespace graphics
#endif #endif