diff --git a/examples/arduino32/stm32DuinoOneButton/generated/SerialTransport.cpp b/examples/arduino32/stm32DuinoOneButton/SerialTransport.cpp similarity index 100% rename from examples/arduino32/stm32DuinoOneButton/generated/SerialTransport.cpp rename to examples/arduino32/stm32DuinoOneButton/SerialTransport.cpp diff --git a/examples/arduino32/stm32DuinoOneButton/generated/SerialTransport.h b/examples/arduino32/stm32DuinoOneButton/SerialTransport.h similarity index 100% rename from examples/arduino32/stm32DuinoOneButton/generated/SerialTransport.h rename to examples/arduino32/stm32DuinoOneButton/SerialTransport.h diff --git a/examples/arduino32/stm32DuinoOneButton/stm32DuinoOneButton.ino b/examples/arduino32/stm32DuinoOneButton/stm32DuinoOneButton.ino index 3a8f754a..44bc564f 100644 --- a/examples/arduino32/stm32DuinoOneButton/stm32DuinoOneButton.ino +++ b/examples/arduino32/stm32DuinoOneButton/stm32DuinoOneButton.ino @@ -7,7 +7,7 @@ // Getting started: https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/tcmenu-overview-quick-start/ // -#include "generated/stm32DuinoOneButton_menu.h" +#include "stm32DuinoOneButton_menu.h" #include void setup() { diff --git a/examples/arduino32/stm32DuinoOneButton/generated/stm32DuinoOneButton_menu.cpp b/examples/arduino32/stm32DuinoOneButton/stm32DuinoOneButton_menu.cpp similarity index 52% rename from examples/arduino32/stm32DuinoOneButton/generated/stm32DuinoOneButton_menu.cpp rename to examples/arduino32/stm32DuinoOneButton/stm32DuinoOneButton_menu.cpp index 7601c91a..172aa600 100644 --- a/examples/arduino32/stm32DuinoOneButton/generated/stm32DuinoOneButton_menu.cpp +++ b/examples/arduino32/stm32DuinoOneButton/stm32DuinoOneButton_menu.cpp @@ -12,9 +12,11 @@ #include #include "stm32DuinoOneButton_menu.h" -#include "../ThemeMonoInverseBuilder.h" +#include "ThemeMonoInverseBuilder.h" #include +#include "TcMenuBuilder.h" + // Global variable declarations const ConnectorLocalInfo applicationInfo = { "One Button", "4fe6e85d-2bbd-4d19-84e5-5d6746883028" }; TcMenuRemoteServer remoteServer(applicationInfo); @@ -27,26 +29,26 @@ NoInitialisationNeeded serialInitializer; SerialTagValueTransport serialTransport(&Serial); TagValueRemoteServerConnection serialConnection(serialTransport, serialInitializer); -// Global Menu Item declarations -const char enumStrSettingsEnumProp_0[] = "Item1"; -const char enumStrSettingsEnumProp_1[] = "Item2"; -const char enumStrSettingsEnumProp_2[] = "Item3"; -const char* const enumStrSettingsEnumProp[] = { enumStrSettingsEnumProp_0, enumStrSettingsEnumProp_1, enumStrSettingsEnumProp_2 }; -const EnumMenuInfo minfoSettingsEnumProp = { "EnumProp", 24, 0xffff, 2, NO_CALLBACK, enumStrSettingsEnumProp }; -EnumMenuItem menuSettingsEnumProp(&minfoSettingsEnumProp, 0, nullptr, INFO_LOCATION_PGM); -const AnalogMenuInfo minfoSettingsIntProp = { "IntProp", 23, 0xffff, 10, NO_CALLBACK, 0, 1, "A" }; -AnalogMenuItem menuSettingsIntProp(&minfoSettingsIntProp, 0, &menuSettingsEnumProp, INFO_LOCATION_PGM); -const BooleanMenuInfo minfoSettingsOption = { "Option", 22, 0xffff, 1, NO_CALLBACK, NAMING_TRUE_FALSE }; -BooleanMenuItem menuSettingsOption(&minfoSettingsOption, false, &menuSettingsIntProp, INFO_LOCATION_PGM); -const SubMenuInfo minfoSettings = { "Settings", 21, 0xffff, 0, NO_CALLBACK }; -BackMenuItem menuBackSettings(&minfoSettings, &menuSettingsOption, INFO_LOCATION_PGM); -SubMenuItem menuSettings(&minfoSettings, &menuBackSettings, nullptr, INFO_LOCATION_PGM); -const AnalogMenuInfo minfoTemp = { "Temp", 20, 0xffff, 100, NO_CALLBACK, 0, 1, "%" }; -AnalogMenuItem menuTemp(&minfoTemp, 0, &menuSettings, INFO_LOCATION_PGM); -const AnyMenuInfo minfoPressMe = { "Press Me", 19, 0xffff, 0, onPressMe }; -ActionMenuItem menuPressMe(&minfoPressMe, &menuTemp, INFO_LOCATION_PGM); +const char* enumSettingValues[] = { "Spanner", "Hammer", "Plane" }; + +void buildMenu(TcMenuBuilder& builder) { + builder.usingDynamicEEPROMStorage() + .actionItem(MENU_PRESS_ME_ID, "Press Me", NoMenuFlags, onPressMe) + .analogBuilder(MENU_TEMP_ID, "Temp", DONT_SAVE, NoMenuFlags) + .maxValue(100).offset(0).divisor(1).unit("%")\ + .endItem() + .subMenu(MENU_SETTINGS_ID, "Settings", NoMenuFlags) + .boolItem(MENU_SETTINGS_OPTION_ID, "Option", DONT_SAVE, NAMING_TRUE_FALSE, NoMenuFlags) + .enumItem(MENU_SETTINGS_ENUM_ID, "Tooling", DONT_SAVE, enumSettingValues, 3, NoMenuFlags) + .analogBuilder(MENU_SETTINGS_INT_ID, "IntProp", DONT_SAVE, NoMenuFlags) + .maxValue(10).offset(0).divisor(1).unit("A").endItem() + .endSub(); +} void setupMenu() { + auto builder = TcMenuBuilder(&MenuManager::ROOT); + buildMenu(builder); + // First we set up eeprom and authentication (if needed). setSizeBasedEEPROMStorageEnabled(false); glBspRom.initialise(0); @@ -55,7 +57,7 @@ void setupMenu() { gfx.begin(); renderer.setUpdatesPerSecond(5); switches.init(internalDigitalIo(), SWITCHES_POLL_EVERYTHING, false); - menuMgr.initWithoutInput(&renderer, &menuPressMe); + menuMgr.initWithoutInput(&renderer, getMenuItemById(MENU_PRESS_ME_ID)); oneButtonHandler.start(); remoteServer.addConnection(&serialConnection); installMonoInverseTitleTheme(renderer, MenuFontDef(&OpenSansRegular8pt, 0), MenuFontDef(&OpenSansRegular8pt, 0), true, BaseGraphicalRenderer::NO_TITLE, true); diff --git a/examples/arduino32/stm32DuinoOneButton/generated/stm32DuinoOneButton_menu.h b/examples/arduino32/stm32DuinoOneButton/stm32DuinoOneButton_menu.h similarity index 80% rename from examples/arduino32/stm32DuinoOneButton/generated/stm32DuinoOneButton_menu.h rename to examples/arduino32/stm32DuinoOneButton/stm32DuinoOneButton_menu.h index 9ffd75f5..18331f02 100644 --- a/examples/arduino32/stm32DuinoOneButton/generated/stm32DuinoOneButton_menu.h +++ b/examples/arduino32/stm32DuinoOneButton/stm32DuinoOneButton_menu.h @@ -34,17 +34,16 @@ extern const UnicodeFont OpenSansRegular8pt[]; // Any externals needed by IO expanders, EEPROMs etc -// Global Menu Item exports -extern EnumMenuItem menuSettingsEnumProp; -extern AnalogMenuItem menuSettingsIntProp; -extern BooleanMenuItem menuSettingsOption; -extern BackMenuItem menuBackSettings; -extern SubMenuItem menuSettings; -extern AnalogMenuItem menuTemp; -extern ActionMenuItem menuPressMe; +// Global Menu Item IDs +#define MENU_PRESS_ME_ID 1 +#define MENU_TEMP_ID 2 +#define MENU_SETTINGS_ID 3 +#define MENU_SETTINGS_OPTION_ID 4 +#define MENU_SETTINGS_INT_ID 5 +#define MENU_SETTINGS_ENUM_ID 6 // Provide a wrapper to get hold of the root menu item and export setupMenu -inline MenuItem& rootMenuItem() { return menuPressMe; } +inline MenuItem& rootMenuItem() { return *getMenuItemById(MENU_PRESS_ME_ID); } void setupMenu(); // Callback functions must always include CALLBACK_FUNCTION after the return type diff --git a/examples/arduino32/stm32DuinoOneButton/generated/tcMenuU8g2.cpp b/examples/arduino32/stm32DuinoOneButton/tcMenuU8g2.cpp similarity index 100% rename from examples/arduino32/stm32DuinoOneButton/generated/tcMenuU8g2.cpp rename to examples/arduino32/stm32DuinoOneButton/tcMenuU8g2.cpp diff --git a/examples/arduino32/stm32DuinoOneButton/generated/tcMenuU8g2.h b/examples/arduino32/stm32DuinoOneButton/tcMenuU8g2.h similarity index 100% rename from examples/arduino32/stm32DuinoOneButton/generated/tcMenuU8g2.h rename to examples/arduino32/stm32DuinoOneButton/tcMenuU8g2.h diff --git a/examples/esp/esp32s2Saola/esp32s2Saola.emf b/examples/esp/esp32s2Saola/esp32s2Saola.emf index 8952c269..063f5271 100644 --- a/examples/esp/esp32s2Saola/esp32s2Saola.emf +++ b/examples/esp/esp32s2Saola/esp32s2Saola.emf @@ -294,6 +294,50 @@ "visible": true, "staticDataInRAM": false } + }, + { + "parentId": 0, + "type": "subMenu", + "item": { + "secured": false, + "name": "IoT Setup", + "id": 22, + "eepromAddress": -1, + "readOnly": false, + "localOnly": false, + "visible": true, + "staticDataInRAM": false + } + }, + { + "parentId": 22, + "type": "textItem", + "defaultValue": "", + "item": { + "textLength": 5, + "itemType": "IP_ADDRESS", + "name": "IP Addr", + "id": 23, + "eepromAddress": -1, + "readOnly": false, + "localOnly": false, + "visible": true, + "staticDataInRAM": false + } + }, + { + "parentId": 22, + "type": "customBuildItem", + "item": { + "menuType": "REMOTE_IOT_MONITOR", + "name": "IoT Monitor1", + "id": 24, + "eepromAddress": -1, + "readOnly": false, + "localOnly": true, + "visible": true, + "staticDataInRAM": false + } } ], "codeOptions": { diff --git a/examples/esp/esp32s2Saola/esp32s2Saola.ino b/examples/esp/esp32s2Saola/esp32s2Saola.ino index 7741afa3..a8c7e64d 100644 --- a/examples/esp/esp32s2Saola/esp32s2Saola.ino +++ b/examples/esp/esp32s2Saola/esp32s2Saola.ino @@ -23,32 +23,53 @@ bool connectedToWiFi = false; void startWiFiAndListener(); -// we add two widgets that show both the connection status and wifi signal strength -// these are added to the renderer and rendered upon any change. +// we add two widgets, one generated by tcMenu Designer Bitmap and Widget Creator, and the inbuilt Wi-Fi widget +// START widgets + // https://tcmenu.github.io/documentation/arduino-libraries//tc-menu/rendering-with-tcmenu-lcd-tft-oled TitleWidget wifiWidget(iconsWifi, 5, 16, 12); +using namespace tcgfx; + +// XBM_LSB_FIRST width=12, height=12, size=24 +// auto size = Coord(12, 12); +const uint8_t myWidgetWidIcon0[] PROGMEM = { + 0x00,0x00,0x8e,0x03,0x51,0x04,0x51,0x04,0x51,0x04,0x51,0x04,0x51,0x04,0x51,0x04,0x59,0x06,0x5d,0x07, + 0x5d,0x07,0x8e,0x03 + }; +// XBM_LSB_FIRST width=12, height=12, size=24 +// auto size = Coord(12, 12); +const uint8_t myWidgetWidIcon1[] PROGMEM = { + 0x00,0x00,0x8e,0x03,0xd7,0x05,0xd7,0x05,0xd3,0x04,0x51,0x04,0x51,0x04,0x51,0x04,0x51,0x04,0x51,0x04, + 0x51,0x04,0x8e,0x03 + }; +const uint8_t* const myWidgetWidIcons[] PROGMEM = { myWidgetWidIcon0, myWidgetWidIcon1 }; + +// See https://tcmenu.github.io/documentation/arduino-libraries//tc-menu/creating-and-using-bitmaps-menu/ +TitleWidget myWidgetWidget(myWidgetWidIcons, 2, 12, 12, nullptr); + +// END widgets + void setup() { // before proceeding, we must start wire and serial, then call setup menu. Serial.begin(115200); serdebugF("Starting ESP32-S2 example"); SPI.begin(36, 37, 35, -1); - EEPROM.begin(512); - setupMenu(); // always call load after setupMenu, as the EEPROM you chose in initialised only after this setupMenu() menuMgr.load(); // set the number of rows in the list. - menuExtrasMyList.setNumberOfRows(42); + getListItemById(EXTRAS_LIST_ID).setNumberOfRows(42); // next start WiFi and register our wifi widget startWiFiAndListener(); auto themeBuilder = TcThemeBuilder(renderer); themeBuilder.addingTitleWidget(wifiWidget); + themeBuilder.addingTitleWidget(myWidgetWidget); // lastly we capture when the root title is pressed present a standard version dialog. setTitlePressedCallback([](int titleCb) { @@ -61,11 +82,13 @@ void loop() { } void startWiFiAndListener() { + TextMenuItem& ssidMenuItem = getTextItemById(CONNECTIVITY_SSID_ID); + TextMenuItem& passphraseMenuItem = getTextItemById(CONNECTIVITY_PASSCODE_ID); // You can choose between station and access point mode by setting the connectivity/Wifi Mode option to your // own choice - if(menuConnectivityWiFiMode.getCurrentValue() == MENU_WIFIMODE_STATION) { + if(asEnumItem(getMenuItemById(CONNECTIVITY_WIFI_MODE_ID)).getCurrentValue() == MENU_WIFIMODE_STATION) { // we are in station mode - WiFi.begin(menuConnectivitySSID.getTextValue(), menuConnectivityPasscode.getTextValue()); + WiFi.begin(ssidMenuItem.getTextValue(), passphraseMenuItem.getTextValue()); WiFi.mode(WIFI_STA); serdebugF("Connecting to Wifi using settings from connectivity menu"); } @@ -74,25 +97,34 @@ void startWiFiAndListener() { WiFi.mode(WIFI_AP); char ssid[25]; char pwd[25]; - copyMenuItemValueDefault(&menuConnectivitySSID, ssid, sizeof ssid, "tcmenu"); - copyMenuItemValueDefault(&menuConnectivityPasscode, pwd, sizeof pwd, "secret"); + copyMenuItemValueDefault(&ssidMenuItem, ssid, sizeof ssid, "tcmenu"); + copyMenuItemValueDefault(&passphraseMenuItem, pwd, sizeof pwd, "secret"); WiFi.softAP(ssid, pwd); serdebugF3("Started up in AP mode, connect with ", ssid, pwd); } - // now monitor the wifi level every second and report it in a widget. - taskManager.scheduleFixedRate(1000, [] { + // + // Here we use the inbuilt task manager to schedule something to run every second. To use + // it you simply provide either a function callback or a lambda as we do here. + // + taskManager.schedule(repeatSeconds(1), [] { + // check the wifi connection status if(WiFi.status() == WL_CONNECTED) { if(!connectedToWiFi) { + // we are fully connected, so we now get the address that DHCP gave us to put into + // the Ip Address item. IPAddress localIp = WiFi.localIP(); Serial.print("Now connected to WiFi"); Serial.println(localIp); - menuConnectivityIPAddress.setIpAddress(localIp[0], localIp[1], localIp[2], localIp[3]); + getIpAddressItemById(CONNECTIVITY_IP_ADDR_ID).setIpAddress(localIp[0], localIp[1], localIp[2], localIp[3]); connectedToWiFi = true; } + + // if the networking is up, then we always render the current signal strength in the title widget. wifiWidget.setCurrentState(fromWiFiRSSITo4StateIndicator(WiFi.RSSI())); } else { + // We've not got networking, set it connectedToWiFi = false; wifiWidget.setCurrentState(0); } @@ -124,7 +156,7 @@ int CALLBACK_FUNCTION fnExtrasMyListRtCall(RuntimeMenuItem* item, uint8_t row, R } void CALLBACK_FUNCTION onListSelected(int id) { - Serial.print("List item select "); Serial.println(menuExtrasMyList.getActiveIndex()); + Serial.print("List item select "); Serial.println(getListItemById(EXTRAS_LIST_ID).getActiveIndex()); } diff --git a/examples/esp/esp32s2Saola/esp32s2Saola_menu.cpp b/examples/esp/esp32s2Saola/esp32s2Saola_menu.cpp index 5e8d6e12..84217c47 100644 --- a/examples/esp/esp32s2Saola/esp32s2Saola_menu.cpp +++ b/examples/esp/esp32s2Saola/esp32s2Saola_menu.cpp @@ -13,6 +13,7 @@ #include #include "esp32s2Saola_menu.h" #include "einkThemeBuilderBlock.h" +#include "TcMenuBuilder.h" // Global variable declarations const PROGMEM ConnectorLocalInfo applicationInfo = { "ESP32-S2 Saola board", "b447b433-fe4f-4ce7-8746-d94bfeefc707" }; @@ -27,76 +28,59 @@ EthernetInitialisation ethernetInitialisation(&server); EthernetTagValTransport ethernetTransport; TagValueRemoteServerConnection ethernetConnection(ethernetTransport, ethernetInitialisation); -// Global Menu Item declarations -const PROGMEM char pgmStrConnectivityAuthenticatorText[] = { "Authenticator" }; -EepromAuthenticationInfoMenuItem menuConnectivityAuthenticator(pgmStrConnectivityAuthenticatorText, NO_CALLBACK, 20, nullptr); -const PROGMEM char pgmStrConnectivityIoTMonitorText[] = { "IoT Monitor" }; -RemoteMenuItem menuConnectivityIoTMonitor(pgmStrConnectivityIoTMonitorText, 19, &menuConnectivityAuthenticator); -const PROGMEM AnyMenuInfo minfoConnectivityIPAddress = { "IP Address", 15, 0xffff, 0, NO_CALLBACK }; -IpAddressMenuItem menuConnectivityIPAddress(&minfoConnectivityIPAddress, IpAddressStorage(127, 0, 0, 1), &menuConnectivityIoTMonitor, INFO_LOCATION_PGM); -const char enumStrConnectivityWiFiMode_0[] PROGMEM = "Station"; -const char enumStrConnectivityWiFiMode_1[] PROGMEM = "Soft AP"; -const char* const enumStrConnectivityWiFiMode[] PROGMEM = { enumStrConnectivityWiFiMode_0, enumStrConnectivityWiFiMode_1 }; -const PROGMEM EnumMenuInfo minfoConnectivityWiFiMode = { "WiFi Mode", 18, 64, 1, NO_CALLBACK, enumStrConnectivityWiFiMode }; -EnumMenuItem menuConnectivityWiFiMode(&minfoConnectivityWiFiMode, 0, &menuConnectivityIPAddress, INFO_LOCATION_PGM); -const PROGMEM AnyMenuInfo minfoConnectivityPasscode = { "Passcode", 17, 42, 0, NO_CALLBACK }; -TextMenuItem menuConnectivityPasscode(&minfoConnectivityPasscode, "", 22, &menuConnectivityWiFiMode, INFO_LOCATION_PGM); -const PROGMEM AnyMenuInfo minfoConnectivitySSID = { "SSID", 16, 20, 0, NO_CALLBACK }; -TextMenuItem menuConnectivitySSID(&minfoConnectivitySSID, "", 22, &menuConnectivityPasscode, INFO_LOCATION_PGM); -const PROGMEM SubMenuInfo minfoConnectivity = { "Connectivity", 14, 0xffff, 0, NO_CALLBACK }; -BackMenuItem menuBackConnectivity(&minfoConnectivity, &menuConnectivitySSID, INFO_LOCATION_PGM); -SubMenuItem menuConnectivity(&minfoConnectivity, &menuBackConnectivity, nullptr, INFO_LOCATION_PGM); -const PROGMEM AnyMenuInfo minfoExtrasMyList = { "My List", 13, 0xffff, 0, onListSelected }; -ListRuntimeMenuItem menuExtrasMyList(&minfoExtrasMyList, 0, fnExtrasMyListRtCall, nullptr, INFO_LOCATION_PGM); -const PROGMEM AnyMenuInfo minfoExtrasColor = { "Color", 12, 16, 0, NO_CALLBACK }; -Rgb32MenuItem menuExtrasColor(&minfoExtrasColor, RgbColor32(0, 0, 0), false, &menuExtrasMyList, INFO_LOCATION_PGM); -const PROGMEM AnyMenuInfo minfoExtrasText = { "Text", 11, 11, 0, NO_CALLBACK }; -TextMenuItem menuExtrasText(&minfoExtrasText, "", 5, &menuExtrasColor, INFO_LOCATION_PGM); -const PROGMEM SubMenuInfo minfoExtras = { "Extras", 10, 0xffff, 0, NO_CALLBACK }; -BackMenuItem menuBackExtras(&minfoExtras, &menuExtrasText, INFO_LOCATION_PGM); -SubMenuItem menuExtras(&minfoExtras, &menuBackExtras, &menuConnectivity, INFO_LOCATION_PGM); -const PROGMEM BooleanMenuInfo minfoDoorOpen = { "Door Open", 5, 10, 1, NO_CALLBACK, NAMING_YES_NO }; -BooleanMenuItem menuDoorOpen(&minfoDoorOpen, false, &menuExtras, INFO_LOCATION_PGM); -const char enumStrFoods_0[] PROGMEM = "Pizza"; -const char enumStrFoods_1[] PROGMEM = "Pasta"; -const char enumStrFoods_2[] PROGMEM = "Salad"; -const char enumStrFoods_3[] PROGMEM = "Pie"; -const char* const enumStrFoods[] PROGMEM = { enumStrFoods_0, enumStrFoods_1, enumStrFoods_2, enumStrFoods_3 }; -const PROGMEM EnumMenuInfo minfoFoods = { "Foods", 4, 8, 3, NO_CALLBACK, enumStrFoods }; -EnumMenuItem menuFoods(&minfoFoods, 0, &menuDoorOpen, INFO_LOCATION_PGM); -const PROGMEM AnalogMenuInfo minfoHalves = { "Halves", 3, 6, 200, NO_CALLBACK, 0, 2, "" }; -AnalogMenuItem menuHalves(&minfoHalves, 0, &menuFoods, INFO_LOCATION_PGM); -const PROGMEM AnalogMenuInfo minfoDecEdit = { "Dec Edit", 2, 4, 1000, NO_CALLBACK, 0, 10, "oC" }; -AnalogMenuItem menuDecEdit(&minfoDecEdit, 0, &menuHalves, INFO_LOCATION_PGM); -const PROGMEM AnalogMenuInfo minfoIntEdit = { "Int Edit", 1, 2, 100, NO_CALLBACK, 0, 1, "%" }; -AnalogMenuItem menuIntEdit(&minfoIntEdit, 0, &menuDecEdit, INFO_LOCATION_PGM); -const PROGMEM AnyMenuInfo minfoHibernate = { "Hibernate", 21, 0xffff, 0, onHibernate }; -ActionMenuItem menuHibernate(&minfoHibernate, &menuIntEdit, INFO_LOCATION_PGM); +const char* enumWiFiMode[] = { "Station", "Soft AP" }; +const char* enumFoodsArray[] = { "Pizza", "Pasta", "Salad", "Pie" }; + +void buildMenu(TcMenuBuilder& builder) { + builder.usingDynamicEEPROMStorage() + .actionItem(HIBERNATE_ID, "Hibernate", NoMenuFlags, onHibernate) + .analogBuilder(INT_EDIT_ID, "Int Edit", ROM_SAVE, NoMenuFlags, 4) + .offset(0).divisor(1).maxValue(100).unit("%").endItem() + .analogBuilder(DEC_EDIT_ID, "Dec Edit", ROM_SAVE, NoMenuFlags, 0) + .offset(0).divisor(100).maxValue(1000).unit("oC").endItem() + .analogBuilder(HALVES_ID, "Halves", ROM_SAVE, MenuFlags().readOnly(), 0) + .offset(0).divisor(2).maxValue(100).unit("").endItem() + .enumItem(FOODS_ID, "Foods", ROM_SAVE, enumFoodsArray, 4, NoMenuFlags) + .boolItem(DOOR_OPEN_ID, "Door Open", ROM_SAVE, NAMING_YES_NO, NoMenuFlags) + .subMenu(EXTRAS_ID, "Extras", NoMenuFlags) + .textItem(EXTRAS_TEXT_ID, "Text", ROM_SAVE, 10, NoMenuFlags) + .rgb32Item(EXTRAS_RGB_ID, "Color", ROM_SAVE, false, NoMenuFlags) + .listItemRtCustom(EXTRAS_LIST_ID, "My List", 10, fnExtrasMyListRtCall, NoMenuFlags) + .endSub() + .subMenu(CONNECTIVITY_ID, "Connectivity", NoMenuFlags) + .textItem(CONNECTIVITY_SSID_ID, "SSID", ROM_SAVE, 20, NoMenuFlags) + .textItem(CONNECTIVITY_PASSCODE_ID, "Passcode", ROM_SAVE, 20, NoMenuFlags) + .enumItem(CONNECTIVITY_WIFI_MODE_ID, "WiFi Mode", ROM_SAVE, enumWiFiMode, 2, NoMenuFlags) + .ipAddressItem(CONNECTIVITY_IP_ADDR_ID, "IP Address", DONT_SAVE, NoMenuFlags) + .remoteConnectivityMonitor(CONNECTIVITY_MON_ID, "IoT Monitor", MenuFlags().localOnly()) + .eepromAuthenticationItem(CONNECTIVITY_AUTH_ID, "Authenticator", MenuFlags().localOnly()) + .endSub(); +} void setupMenu() { + auto builder = TcMenuBuilder(&MenuManager::ROOT); + buildMenu(builder); + // First we set up eeprom and authentication (if needed). setSizeBasedEEPROMStorageEnabled(false); glEspRom.init(); menuMgr.setEepromRef(&glEspRom); authManager.initialise(menuMgr.getEepromAbstraction(), 200); menuMgr.setAuthenticator(&authManager); - // Now add any readonly, non-remote and visible flags. - menuConnectivityIoTMonitor.setLocalOnly(true); - menuConnectivityAuthenticator.setLocalOnly(true); // Code generated by plugins and new operators. display.init(115200, true, 10, false); display.setRotation(0); renderer.setUpdatesPerSecond(1); switches.init(internalDigitalIo(), SWITCHES_POLL_EVERYTHING, true); - menuMgr.initFor4WayJoystick(&renderer, &menuHibernate, 2, 1, 3, 4, -1, 20); + menuMgr.initFor4WayJoystick(&renderer, builder.getRootItem(), 2, 1, 3, 4, -1, 20); remoteServer.addConnection(ðernetConnection); applyTheme(renderer); // We have an IoT monitor, register the server - menuConnectivityIoTMonitor.setRemoteServer(remoteServer); + getIoTRemoteMenuById(CONNECTIVITY_MON_ID).setRemoteServer(remoteServer); // We have an EEPROM authenticator, it needs initialising - menuConnectivityAuthenticator.init(); + getAuthenticationMenuById(CONNECTIVITY_AUTH_ID).init(); } diff --git a/examples/esp/esp32s2Saola/esp32s2Saola_menu.h b/examples/esp/esp32s2Saola/esp32s2Saola_menu.h index e26cd9bb..c8a963e2 100644 --- a/examples/esp/esp32s2Saola/esp32s2Saola_menu.h +++ b/examples/esp/esp32s2Saola/esp32s2Saola_menu.h @@ -37,29 +37,26 @@ extern EthernetInitialisation ethernetInitialisation; // Any externals needed by IO expanders, EEPROMs etc extern EspPreferencesEeprom glEspRom; -// Global Menu Item exports -extern EepromAuthenticationInfoMenuItem menuConnectivityAuthenticator; -extern RemoteMenuItem menuConnectivityIoTMonitor; -extern IpAddressMenuItem menuConnectivityIPAddress; -extern EnumMenuItem menuConnectivityWiFiMode; -extern TextMenuItem menuConnectivityPasscode; -extern TextMenuItem menuConnectivitySSID; -extern BackMenuItem menuBackConnectivity; -extern SubMenuItem menuConnectivity; -extern ListRuntimeMenuItem menuExtrasMyList; -extern Rgb32MenuItem menuExtrasColor; -extern TextMenuItem menuExtrasText; -extern BackMenuItem menuBackExtras; -extern SubMenuItem menuExtras; -extern BooleanMenuItem menuDoorOpen; -extern EnumMenuItem menuFoods; -extern AnalogMenuItem menuHalves; -extern AnalogMenuItem menuDecEdit; -extern AnalogMenuItem menuIntEdit; -extern ActionMenuItem menuHibernate; +#define HIBERNATE_ID 21 +#define INT_EDIT_ID 1 +#define DEC_EDIT_ID 2 +#define HALVES_ID 3 +#define FOODS_ID 4 +#define DOOR_OPEN_ID 5 +#define EXTRAS_ID 6 +#define EXTRAS_TEXT_ID 11 +#define EXTRAS_RGB_ID 12 +#define EXTRAS_LIST_ID 13 +#define CONNECTIVITY_ID 14 +#define CONNECTIVITY_SSID_ID 15 +#define CONNECTIVITY_PASSCODE_ID 16 +#define CONNECTIVITY_WIFI_MODE_ID 17 +#define CONNECTIVITY_IP_ADDR_ID 18 +#define CONNECTIVITY_MON_ID 19 +#define CONNECTIVITY_AUTH_ID 20 // Provide a wrapper to get hold of the root menu item and export setupMenu -inline MenuItem& rootMenuItem() { return menuHibernate; } +inline MenuItem& rootMenuItem() { return *getMenuItemById(HIBERNATE_ID); } void setupMenu(); // Callback functions must always include CALLBACK_FUNCTION after the return type diff --git a/src/EditableLargeNumberMenuItem.h b/src/EditableLargeNumberMenuItem.h index 4ce81487..c83f15da 100644 --- a/src/EditableLargeNumberMenuItem.h +++ b/src/EditableLargeNumberMenuItem.h @@ -208,6 +208,12 @@ class EditableLargeNumberMenuItem : public EditableMultiPartMenuItem { negativeAllowed = allowNeg; } + EditableLargeNumberMenuItem(const AnyMenuInfo* info, RuntimeRenderingFn renderFn, const LargeFixedNumber& initial, bool allowNeg, MenuItem* next = nullptr, bool isPgm = INFO_LOCATION_PGM) + : EditableMultiPartMenuItem(info, isPgm, MENUTYPE_LARGENUM_VALUE, initial.getTotalDigits() + (allowNeg ? 1 : 0), renderFn, next) { + data = initial; + negativeAllowed = allowNeg; + } + /** gets the large integer value that this class is using */ LargeFixedNumber* getLargeNumber() { return &data; } diff --git a/src/EepromItemStorage.cpp b/src/EepromItemStorage.cpp index 42956924..ede4ddea 100644 --- a/src/EepromItemStorage.cpp +++ b/src/EepromItemStorage.cpp @@ -9,7 +9,7 @@ #include "ScrollChoiceMenuItem.h" #include "MenuIterator.h" -bool tcMenuUseSizedEeprom = false; +TcEepromStorageMode tcStorageMode = TC_STORE_ROM_LEGACY; uint16_t saveRecursively(EepromAbstraction* eeprom, MenuItem* nextMenuItem) { uint16_t lastItemSaved = 0; @@ -80,11 +80,16 @@ void saveMenuItem(EepromAbstraction* eeprom, MenuItem* nextMenuItem) { } void saveMenuStructure(EepromAbstraction* eeprom, uint16_t magicKey) { - serlogF2(SER_TCMENU_INFO, "Save to EEPROM with key ", magicKey); - eeprom->write16(0, magicKey); - uint16_t maxPos = saveRecursively(eeprom, menuMgr.getRoot()); - if(tcMenuUseSizedEeprom) { - eeprom->write16(2, maxPos); + serlogF3(SER_TCMENU_INFO, "Save to EEPROM with key, mode ", magicKey, tcStorageMode); + if (tcStorageMode == TC_STORE_ROM_DYNAMIC) { + DynamicEepromStore dynamic; + dynamic.saveMenuStructure(eeprom, magicKey); + } else { + eeprom->write16(0, magicKey); + uint16_t maxPos = saveRecursively(eeprom, menuMgr.getRoot()); + if(tcStorageMode == TC_STORE_ROM_WITH_SIZE) { + eeprom->write16(2, maxPos); + } } } @@ -164,8 +169,13 @@ void loadRecursively(EepromAbstraction* eeprom, MenuItem* nextMenuItem, uint16_t } bool loadMenuStructure(EepromAbstraction* eeprom, uint16_t magicKey) { + if (tcStorageMode == TC_STORE_ROM_DYNAMIC) { + DynamicEepromStore dynamic; + return dynamic.loadMenuStructure(eeprom, magicKey); + } + if (eeprom->read16(0) == magicKey) { - uint16_t maxEntry = (tcMenuUseSizedEeprom) ? eeprom->read16(2) : 0xFFFE; + uint16_t maxEntry = (tcStorageMode == TC_STORE_ROM_WITH_SIZE) ? eeprom->read16(2) : 0xFFFE; serlogFHex(SER_TCMENU_INFO, "Load from EEPROM key found ", magicKey); MenuItem* nextMenuItem = menuMgr.getRoot(); loadRecursively(eeprom, nextMenuItem, maxEntry); @@ -178,6 +188,8 @@ bool loadMenuStructure(EepromAbstraction* eeprom, uint16_t magicKey) { } bool loadMenuItem(EepromAbstraction* eeprom, MenuItem* theItem, uint16_t magicKey) { + if (tcStorageMode == TC_STORE_ROM_DYNAMIC) return false; // cant read single item + bool tcMenuUseSizedEeprom = tcStorageMode == TC_STORE_ROM_WITH_SIZE; if (eeprom->read16(0) == magicKey && (!tcMenuUseSizedEeprom || eeprom->read16(2) <= theItem->getEepromPosition())) { loadSingleItem(eeprom, theItem); return true; @@ -196,5 +208,183 @@ void triggerAllChangedCallbacks() { } void setSizeBasedEEPROMStorageEnabled(bool ena) { - tcMenuUseSizedEeprom = ena; + setEepromStorageMode(ena ? TC_STORE_ROM_WITH_SIZE : TC_STORE_ROM_LEGACY); +} + +void setEepromStorageMode(TcEepromStorageMode mode) { + tcStorageMode = mode; +} + +#define MAX_ALLOWABLE 0x7FFF + +/** + * We load items from the eeprom one at a time, each item is in memory has a two byte ID, then two bytes + * for size, followed by the data for that size. This allows up to 64K of data for each item. An ID and size of + * zero indicate end of stream + * + * | ID | Size | Data | + * | 1 | 2 | 0x0001 | + * | 0 | 0 | none | + * + * @param eeprom + * @param magicKey + * @return + */ +bool DynamicEepromStore::loadMenuStructure(EepromAbstraction *eeprom, uint16_t magicKey) { + if (eeprom->read16(0) != magicKey || tcStorageMode != TC_STORE_ROM_DYNAMIC) { + return false; + } + serlogF2(SER_TCMENU_INFO, "Load dynamic EEPROM with key ", magicKey); + + uint16_t position = 2; // Start after magic key + + while (position < MAX_ALLOWABLE) { + uint16_t itemId = eeprom->read16(position); + position += 2; + + uint16_t dataLength = eeprom->read16(position); + position += 2; + + if (itemId == 0 || dataLength == 0) { + return true; // End of stored items + } + + MenuItem *item = getMenuItemById(itemId); + if (item != nullptr) { + // Temporarily set the eeprom position to where the data is stored + loadItemFromRom(eeprom, item, position, dataLength); + } + + position += dataLength; + } + + return false; +} + +bool DynamicEepromStore::saveMenuStructure(EepromAbstraction *eeprom, uint16_t magicKey) { + if (eeprom->read16(0) != magicKey || tcStorageMode != TC_STORE_ROM_DYNAMIC) { + return false; + } + + uint16_t position = 2; + MenuItemIterator iterator;; + MenuItem *next; + while ((next = iterator.nextItem()) != nullptr) { + if (next->getEepromPosition() == 0xFFFF) continue; + size_t written = saveItemDynamically(eeprom, next, position + 2); + if (written > 0) { + eeprom->write16(position, next->getId()); + position += written + 2; + } + } + return true; +} + +void DynamicEepromStore::loadItemFromRom(EepromAbstraction* eeprom, MenuItem* nextMenuItem, EepromPosition pos, size_t len) { + if (nextMenuItem->getEepromPosition() == 0xFFFF) return; + auto menuType = nextMenuItem->getMenuType(); + if (menuType == MENUTYPE_TEXT_VALUE) { + auto textItem = asTextItem(nextMenuItem); + eeprom->readCharArrIntoMemArray(const_cast(textItem.getTextValue()), pos, textItem.textLength()); + textItem.cleanUpArray(); + textItem.setChanged(true); + } + else if (menuType == MENUTYPE_TIME) { + auto timeItem = asTimeItem(nextMenuItem); + eeprom->readIntoMemArray(reinterpret_cast(timeItem.getUnderlyingData()), pos, 4); + timeItem.setChanged(true); + } + else if (menuType == MENUTYPE_DATE) { + auto dateItem = asDateItem(nextMenuItem); + eeprom->readIntoMemArray(reinterpret_cast(dateItem.getUnderlyingData()), pos, 4); + dateItem.setChanged(true); + } + else if (menuType == MENUTYPE_IPADDRESS) { + auto ipItem = asIpAddressItem(nextMenuItem); + eeprom->readIntoMemArray(ipItem.getIpAddress(), pos, 4); + ipItem.setChanged(true); + } + else if (menuType == MENUTYPE_SCROLLER_VALUE) { + auto scroller = asScrollChoiceItem(nextMenuItem); + scroller.setCurrentValue(eeprom->read16(pos), true); + } + else if (menuType == MENUTYPE_COLOR_VALUE) { + auto rgb = reinterpret_cast(nextMenuItem); + auto data = rgb->getUnderlying(); + data->red = eeprom->read8(pos); + data->green = eeprom->read8(pos + 1); + data->blue = eeprom->read8(pos + 2); + data->alpha = eeprom->read8(pos + 3); + rgb->setChanged(true); + } + else if (menuType == MENUTYPE_LARGENUM_VALUE) { + auto numItem = asLargeNumberItem(nextMenuItem); + numItem.getLargeNumber()->setNegative(eeprom->read8(pos)); + eeprom->readIntoMemArray(numItem.getLargeNumber()->getNumberBuffer(), pos + 1, 6); + numItem.setChanged(true); + } + else if (menuType == MENUTYPE_INT_VALUE || menuType == MENUTYPE_ENUM_VALUE || menuType == MENUTYPE_BOOLEAN_VALUE) { + auto intItem = reinterpret_cast(nextMenuItem); + intItem->setCurrentValue(eeprom->read16(pos), true); + } +} + +size_t DynamicEepromStore::saveItemDynamically(EepromAbstraction *eeprom, MenuItem *nextMenuItem, uint16_t pos) { + auto menuType = nextMenuItem->getMenuType(); + if (menuType == MENUTYPE_TEXT_VALUE) { + auto textItem = asTextItem(nextMenuItem); + eeprom->write16(pos, textItem.textLength()); + eeprom->writeCharArrToRom(pos + 2, textItem.getTextValue(), textItem.textLength()); + return textItem.textLength(); + } + else if (menuType == MENUTYPE_TIME) { + auto timeItem = asTimeItem(nextMenuItem); + eeprom->write16(pos, 4); + eeprom->writeArrayToRom(pos + 2, reinterpret_cast(timeItem.getUnderlyingData()), 4); + return 4; + } + else if (menuType == MENUTYPE_DATE) { + auto dateItem = asDateItem(nextMenuItem); + eeprom->write16(pos, 4); + eeprom->writeArrayToRom(pos + 2, reinterpret_cast(dateItem.getUnderlyingData()), 4); + return 4; + } + else if (menuType == MENUTYPE_IPADDRESS) { + auto ipItem = asIpAddressItem(nextMenuItem); + eeprom->write16(pos, 4); + eeprom->writeArrayToRom(pos + 2, ipItem.getIpAddress(), 4); + return 4; + } + else if (menuType == MENUTYPE_SCROLLER_VALUE) { + auto scroller = asScrollChoiceItem(nextMenuItem); + eeprom->write16(pos, 2); + eeprom->write16(pos + 2, scroller.getCurrentValue()); + return 2; + } + else if (menuType == MENUTYPE_COLOR_VALUE) { + auto rgb = reinterpret_cast(nextMenuItem); + auto data = rgb->getUnderlying(); + eeprom->write16(pos, 4); + eeprom->write8(pos + 2, data->red); + eeprom->write8(pos + 3, data->green); + eeprom->write8(pos + 4, data->blue); + eeprom->write8(pos + 5, data->alpha); + rgb->setChanged(true); + return 4; + } + else if (menuType == MENUTYPE_LARGENUM_VALUE) { + auto numItem = asLargeNumberItem(nextMenuItem); + eeprom->write16(pos, 8); + numItem.getLargeNumber()->setNegative(eeprom->read8(pos)); + eeprom->readIntoMemArray(numItem.getLargeNumber()->getNumberBuffer(), pos + 1, 6); + numItem.setChanged(true); + return 8; + } + else if (menuType == MENUTYPE_INT_VALUE || menuType == MENUTYPE_ENUM_VALUE || menuType == MENUTYPE_BOOLEAN_VALUE) { + auto intItem = reinterpret_cast(nextMenuItem); + eeprom->write16(pos, 2); + eeprom->write16(pos + 2, intItem->getCurrentValue()); + return 2; + } + return -1; } diff --git a/src/EepromItemStorage.h b/src/EepromItemStorage.h index e36bd3db..3a5883f3 100644 --- a/src/EepromItemStorage.h +++ b/src/EepromItemStorage.h @@ -62,4 +62,20 @@ void triggerAllChangedCallbacks(); */ void setSizeBasedEEPROMStorageEnabled(bool ena); +enum TcEepromStorageMode { + TC_STORE_ROM_LEGACY, + TC_STORE_ROM_WITH_SIZE, + TC_STORE_ROM_DYNAMIC +}; + +void setEepromStorageMode(TcEepromStorageMode mode); + +class DynamicEepromStore { + static void loadItemFromRom(EepromAbstraction* eeprom, MenuItem* nextMenuItem, EepromPosition pos, size_t len); + static size_t saveItemDynamically(EepromAbstraction * eeprom, MenuItem * next, uint16_t position); +public: + bool loadMenuStructure(EepromAbstraction* eeprom, uint16_t magicKey = 0xf00d); + bool saveMenuStructure(EepromAbstraction* eeprom, uint16_t magicKey = 0xf00d); +}; + #endif //_EEPROM_ITEM_STORAGE_H_ diff --git a/src/MenuItems.h b/src/MenuItems.h index b9ea72d8..d3fb8e52 100755 --- a/src/MenuItems.h +++ b/src/MenuItems.h @@ -192,6 +192,15 @@ struct FloatMenuInfo { MenuCallbackFn callback; }; +union AllMenuInfoTypes { + AnyMenuInfo anyInfo; + AnalogMenuInfo analogInfo; + EnumMenuInfo enumInfo; + BooleanMenuInfo booleanInfo; + SubMenuInfo subInfo; + FloatMenuInfo floatInfo; +}; + /** * Each menu item can be in the following states. */ diff --git a/src/MenuIterator.cpp b/src/MenuIterator.cpp index 57383b49..be6cc402 100644 --- a/src/MenuIterator.cpp +++ b/src/MenuIterator.cpp @@ -167,6 +167,14 @@ MenuItem* MenuItemIterator::currentParent() { else return parentItems[level - 1]; } +RemoteMenuItem & asIoTRemoteItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_RUNTIME_LIST, "Item not list"); +} + +EepromAuthenticationInfoMenuItem & asAuthenticationMenuItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_RUNTIME_LIST, "Item not list"); +} + bool RemoteNoMenuItemPredicate::matches(MenuItem* item) { if(item->getMenuType() == MENUTYPE_SUB_VALUE) { return !item->isLocalOnly(); diff --git a/src/MenuIterator.h b/src/MenuIterator.h index d670ab8d..ae21d315 100644 --- a/src/MenuIterator.h +++ b/src/MenuIterator.h @@ -4,6 +4,7 @@ */ #include +#include "ScrollChoiceMenuItem.h" /** * @file MenuIterator.h @@ -17,6 +18,7 @@ # define MAX_MENU_DEPTH 4 #endif // MAX_MENU_DEPTH +class EditableLargeNumberMenuItem; // forward reference of menu item class MenuItem; @@ -211,4 +213,103 @@ class MenuItemIterator { MenuItem* currentParent(); }; +template +inline T *asMenuItem(MenuItem *item, MenuType expectedType, const char *typeName) { + if (item == nullptr || item->getMenuType() != expectedType) { + while (1) { + // literally stop here if we get a fault, continuing will lead to worse. + serlogF2(SER_ERROR, typeName, item == nullptr ? 0 : item->getId()); + delay(1000); + } + } + return reinterpret_cast(item); +} + +inline AnalogMenuItem &asAnalogItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_INT_VALUE, "Item not analog"); +} +inline AnalogMenuItem& getAnalogItemById(menuid_t id) { return asAnalogItem(getMenuItemById(id)); } + + +inline EnumMenuItem &asEnumItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_ENUM_VALUE, "Item not enum"); +} +inline EnumMenuItem& getEnumItemById(menuid_t id) { return asEnumItem(getMenuItemById(id)); } + + +inline BooleanMenuItem &asBooleanItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_BOOLEAN_VALUE, "Item not bool"); +} +inline BooleanMenuItem& getBooleanItemById(menuid_t id) { return asBooleanItem(getMenuItemById(id)); } + +inline TextMenuItem& asTextItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_TEXT_VALUE, "Item not text"); +} +inline TextMenuItem& getTextItemById(menuid_t id) { return asTextItem(getMenuItemById(id)); } + +inline FloatMenuItem& asFloatItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_FLOAT_VALUE, "Item not float"); +} +inline FloatMenuItem& getFloatItemById(menuid_t id) { return asFloatItem(getMenuItemById(id)); } + +inline SubMenuItem &asSubMenu(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_SUB_VALUE, "Item not submenu"); +} +inline SubMenuItem& getSubMenuById(menuid_t id) { return asSubMenu(getMenuItemById(id)); } + +inline ActionMenuItem &asActionItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_ACTION_VALUE, "Item not action"); +} +inline ActionMenuItem& getActionItemById(menuid_t id) { return asActionItem(getMenuItemById(id)); } + +inline IpAddressMenuItem &asIpAddressItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_IPADDRESS, "Item not IP"); +} +inline IpAddressMenuItem& getIpAddressItemById(menuid_t id) { return asIpAddressItem(getMenuItemById(id)); } + +inline BackMenuItem &asBackMenu(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_BACK_VALUE, "Item not back"); +} +inline BackMenuItem& getBackMenuById(menuid_t id) { return asBackMenu(getMenuItemById(id)); } + +inline ScrollChoiceMenuItem &asScrollChoiceItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_SCROLLER_VALUE, "Item not scroll"); +} +inline ScrollChoiceMenuItem& getScrollChoiceItemById(menuid_t id) { return asScrollChoiceItem(getMenuItemById(id)); } + +inline TimeFormattedMenuItem &asTimeItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_TIME, "Item not time"); +} +inline TimeFormattedMenuItem& getTimeItemById(menuid_t id) { return asTimeItem(getMenuItemById(id)); } + +inline DateFormattedMenuItem &asDateItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_DATE, "Item not date"); +} +inline DateFormattedMenuItem& getDateItemById(menuid_t id) { return asDateItem(getMenuItemById(id)); } + +inline Rgb32MenuItem &asRgb32Item(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_COLOR_VALUE, "Item not rgb32"); +} +inline Rgb32MenuItem& getRgb32ItemById(menuid_t id) { return asRgb32Item(getMenuItemById(id)); } + +inline EditableLargeNumberMenuItem &asLargeNumberItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_LARGENUM_VALUE, "Item not lgenum"); +} +inline EditableLargeNumberMenuItem& getLargeNumberItemById(menuid_t id) { return asLargeNumberItem(getMenuItemById(id)); } + +inline ListRuntimeMenuItem &asListItem(MenuItem *item) { + return *asMenuItem(item, MENUTYPE_RUNTIME_LIST, "Item not list"); +} +inline ListRuntimeMenuItem& getListItemById(menuid_t id) { return asListItem(getMenuItemById(id)); } + +class RemoteMenuItem; // forward declaration +class EepromAuthenticationInfoMenuItem; // forward declaration + +RemoteMenuItem& asIoTRemoteItem(MenuItem *item); +inline RemoteMenuItem& getIoTRemoteMenuById(menuid_t id) { return asIoTRemoteItem(getMenuItemById(id)); } + +EepromAuthenticationInfoMenuItem& asAuthenticationMenuItem(MenuItem *item); +inline EepromAuthenticationInfoMenuItem& getAuthenticationMenuById(menuid_t id) { return asAuthenticationMenuItem(getMenuItemById(id));} + + #endif diff --git a/src/TcMenuBuilder.cpp b/src/TcMenuBuilder.cpp new file mode 100644 index 00000000..ee3584aa --- /dev/null +++ b/src/TcMenuBuilder.cpp @@ -0,0 +1,340 @@ +// +// Created by David Cherry on 22/01/2026. +// + +#include "TcMenuBuilder.h" + +#include "RemoteMenuItem.h" +#include "ScrollChoiceMenuItem.h" +#include "tcMenu.h" + +void MenuFlags::setOnMenuItem(MenuItem* item) const { + item->setReadOnly((flags & INT_MENU_FLAG_READ)!=0); + item->setSecured((flags & INT_MENU_FLAG_SECURE)!=0); + item->setLocalOnly((flags & INT_MENU_FLAG_LOCAL)!=0); + if ((flags & INT_MENU_FLAG_HIDE)!=0) { + item->setVisible(false); + } +} + +BtreeList infoAllocator; + +void TcMenuBuilder::putAtEndOfSub(MenuItem * toAdd) const { + toAdd->setNext(nullptr); + + if (currentSub->getChild() == nullptr) { + serlogF3(SER_TCMENU_INFO, "New child ", currentSub->getId(), toAdd->getId()); + currentSub->setChild(toAdd); + return; + } + + int loopCount = 0; + auto item = currentSub->getChild(); + while (item && item->getNext() && loopCount < 999) { + ++loopCount; + item = item->getNext(); + } + if (item) { + serlogF3(SER_TCMENU_INFO, "Append child ", currentSub->getId(), toAdd->getId()); + item->setNext(toAdd); + } else { + serlogF3(SER_ERROR, "PutAtEnd failed ", toAdd->getId(), loopCount); + } +} + +AnyInfoReserve* TcMenuBuilder::fillInAnyInfo(menuid_t id, const char *name, int eeprom, int maxVal, MenuCallbackFn callback_fn) { + AnyInfoReserve reserve; + reserve.getInfo()->anyInfo.id = id; + strncpy(reserve.getInfo()->anyInfo.name, name, NAME_SIZE_T); + reserve.getInfo()->anyInfo.name[NAME_SIZE_T-1] = 0; + reserve.getInfo()->anyInfo.eepromAddr = eeprom; + reserve.getInfo()->anyInfo.callback = callback_fn; + reserve.getInfo()->anyInfo.maxValue = maxVal; + infoAllocator.add(reserve); + return infoAllocator.getByKey(id); +} + +TcMenuBuilder & TcMenuBuilder::appendCustomItem(MenuItem *itemToAdd) { + putAtEndOfSub(itemToAdd); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::endSub() { + if (parent != nullptr) { + return *parent; + } else { + return *this; + } +} + +AnalogItemBuilder & AnalogItemBuilder::unit(const char *unit) { + if (unit == nullptr) { + info.unitName[0] = '\0'; + } else { + strncpy(info.unitName, unit, sizeof info.unitName); + info.unitName[sizeof info.unitName - 1] = '\0'; + } + return *this; +} + +ScrollChoiceBuilder & ScrollChoiceBuilder::fromRamChoices(const char *fixedArray, int numItems, int fixedItemSize) { + createdItem = new ScrollChoiceMenuItem(&info, initialValue, fixedArray, fixedItemSize, numItems); + return *this; +} + +ScrollChoiceBuilder & ScrollChoiceBuilder::fromRomChoices(EepromPosition choices, int numItems, int fixedItemSize) { + createdItem = new ScrollChoiceMenuItem(&info, initialValue, choices, fixedItemSize, numItems); + return *this; +} + +ScrollChoiceBuilder & ScrollChoiceBuilder::ofCustomRtFunction(RuntimeRenderingFn rtRenderFn, int numItems) { + createdItem = new ScrollChoiceMenuItem(&info, rtRenderFn, initialValue, numItems, nullptr, false); + return *this; +} + +ScrollChoiceBuilder & ScrollChoiceBuilder::cachingEepromValues() { + if (createdItem != nullptr) { + createdItem->cacheEepromValues(); + } + return *this; +} + +TcMenuBuilder& ScrollChoiceBuilder::endItem() const { + if (createdItem == nullptr) { + serlogF(SER_ERROR, "ScrollChoiceBuilder::commit null!"); + } else { + menuFlags.setOnMenuItem(createdItem); + parentBuilder.putAtEndOfSub(createdItem); + } + return parentBuilder; +} + +TcMenuBuilder& TcMenuBuilder::usingDynamicEEPROMStorage() { + setEepromStorageMode(TC_STORE_ROM_DYNAMIC); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::floatItem(menuid_t id, const char *name, EepromPosition eepromPosition, uint16_t decimalPlaces, + MenuFlags flags, float initial, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, 0xFFFF, decimalPlaces, callbackFn); + auto item = new FloatMenuItem(&reserve->getInfo()->floatInfo, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::actionItem(menuid_t id, const char *name, MenuFlags flags, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, 0xFFFF, 0, callbackFn); + auto item = new ActionMenuItem(&reserve->getInfo()->anyInfo, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::boolItem(menuid_t id, const char *name, EepromPosition eepromPosition, BooleanNaming naming, + MenuFlags flags, bool initial, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 1, callbackFn); + reserve->getInfo()->booleanInfo.naming = naming; + auto item = new BooleanMenuItem(&reserve->getInfo()->booleanInfo, initial, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +AnalogItemBuilder TcMenuBuilder::analogBuilder(menuid_t id, const char *name, EepromPosition eepromPosition, + MenuFlags flags, uint16_t initialValue, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 0, callbackFn); + auto item = new AnalogMenuItem(&reserve->getInfo()->analogInfo, initialValue, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return AnalogItemBuilder(*item, reserve->getInfo()->analogInfo, *this); +} + +TcMenuBuilder & TcMenuBuilder::enumItem(menuid_t id, const char *name, EepromPosition eepromPosition, + const char **enumEntries, uint16_t numEntries, MenuFlags flags, uint16_t value, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, numEntries - 1, callbackFn); + reserve->getInfo()->enumInfo.menuItems = enumEntries; + auto item = new EnumMenuItem(&reserve->getInfo()->enumInfo, value, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::ipAddressItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, IpAddressStorage initial, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 0, callbackFn); + auto item = new IpAddressMenuItem(&reserve->getInfo()->anyInfo, initial, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder & TcMenuBuilder::timeItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, MultiEditWireType timeFormat, + const TimeStorage &timeStorage, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 0, callbackFn); + auto item = new TimeFormattedMenuItem(&reserve->getInfo()->anyInfo, timeStorage, timeFormat, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder & TcMenuBuilder::timeItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, + MultiEditWireType timeFormat, MenuCallbackFn callbackFn) { + return timeItem(id, name, eepromPosition, flags, timeFormat, TimeStorage(12, 0), callbackFn); +} + +TcMenuBuilder & TcMenuBuilder::dateItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, + DateStorage initial, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 0, callbackFn); + auto item = new DateFormattedMenuItem(&reserve->getInfo()->anyInfo, initial, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder & TcMenuBuilder::dateItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, MenuCallbackFn callbackFn) { + return dateItem(id, name, eepromPosition, flags, DateStorage(1, 1, 2000), callbackFn); +} + +ScrollChoiceBuilder TcMenuBuilder::scrollChoiceBuilder(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, uint16_t initialValue, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 0, callbackFn); + return ScrollChoiceBuilder(reserve->getInfo()->anyInfo, *this, initialValue, flags); +} + +TcMenuBuilder & TcMenuBuilder::rgb32Item(menuid_t id, const char *name, EepromPosition eepromPosition, + bool alphaChannel, MenuFlags flags, MenuCallbackFn callbackFn) { + return rgb32Item(id, name, eepromPosition, alphaChannel, flags, RgbColor32(0, 0, 0), callbackFn); +} + +TcMenuBuilder & TcMenuBuilder::rgb32Item(menuid_t id, const char *name, EepromPosition eepromPosition, bool alphaChannel, MenuFlags flags, const RgbColor32& initial, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 3, callbackFn); + auto item = new Rgb32MenuItem(&reserve->getInfo()->anyInfo, initial, alphaChannel, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::rgb32CustomRt(menuid_t id, const char *name, EepromPosition eepromPosition, bool alphaChannel, + RuntimeRenderingFn renderFn, MenuFlags flags, const RgbColor32& initial, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 3, callbackFn); + auto item = new Rgb32MenuItem(&reserve->getInfo()->anyInfo, renderFn, initial, alphaChannel, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::largeNumberRtCustom(menuid_t id, const char *name, EepromPosition eepromPosition, const LargeFixedNumber& num, + bool allowNegative, RuntimeRenderingFn renderFn, MenuFlags flags, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 0, callbackFn); + auto item = new EditableLargeNumberMenuItem(&reserve->getInfo()->anyInfo, renderFn, num, allowNegative, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::largeNumberItem(menuid_t id, const char *name, EepromPosition eepromPosition, const LargeFixedNumber& num, bool allowNegative, MenuFlags flags, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 0, callbackFn); + auto item = new EditableLargeNumberMenuItem(&reserve->getInfo()->anyInfo, num, allowNegative, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + + +TcMenuBuilder & TcMenuBuilder::listItemRam(menuid_t id, const char *name, uint16_t numberOfRows, const char** arrayOfItems, MenuFlags flags, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, 0xFFFF, numberOfRows, callbackFn); + auto item = new ListRuntimeMenuItem(&reserve->getInfo()->anyInfo, numberOfRows, arrayOfItems, ListRuntimeMenuItem::RAM_ARRAY, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder & TcMenuBuilder::listItemFlash(menuid_t id, const char *name, uint16_t numberOfRows, const char **arrayOfItems, MenuFlags flags, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, 0xFFFF, numberOfRows, callbackFn); + auto item = new ListRuntimeMenuItem(&reserve->getInfo()->anyInfo, numberOfRows, arrayOfItems, ListRuntimeMenuItem::FLASH_ARRAY, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder & TcMenuBuilder::listItemRtCustom(menuid_t id, const char* name, uint16_t numberOfRows, RuntimeRenderingFn rtRenderFn, MenuFlags flags, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, 0xFFFF, numberOfRows, callbackFn); + auto item = new ListRuntimeMenuItem(&reserve->getInfo()->anyInfo, numberOfRows, rtRenderFn, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder & TcMenuBuilder::eepromAuthenticationItem(menuid_t id, const char *name, MenuFlags flags,MenuCallbackFn onAuthChanged) { + auto item = new EepromAuthenticationInfoMenuItem(name, onAuthChanged, id, nullptr); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::remoteConnectivityMonitor(menuid_t id, const char* name, MenuFlags flags) { + auto item = new RemoteMenuItem(name, id, nullptr); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::ipAddressItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, MenuCallbackFn callbackFn) { + return ipAddressItem(id, name, eepromPosition, flags, IpAddressStorage(127, 0, 0, 1), callbackFn); +} + +TcMenuBuilder& TcMenuBuilder::ipAddressCustomRt(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, + RuntimeRenderingFn renderFn, IpAddressStorage ipInitial, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 3, callbackFn); + auto item = new IpAddressMenuItem(&reserve->getInfo()->anyInfo, renderFn, ipInitial, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::dateItemCustomRt(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, DateStorage initial, + RuntimeRenderingFn renderFn, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 3, callbackFn); + auto item = new DateFormattedMenuItem(&reserve->getInfo()->anyInfo, renderFn, initial, id, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::timeItemCustomRt(menuid_t id, const char *name, EepromPosition eepromPosition, const TimeStorage& timeStorage, + RuntimeRenderingFn renderFn, MenuFlags flags, MultiEditWireType timeFormat, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, 3, callbackFn); + auto item = new TimeFormattedMenuItem(&reserve->getInfo()->anyInfo, renderFn, timeStorage, timeFormat, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::textItem(menuid_t id, const char *name, EepromPosition eepromPosition, uint16_t textLength, + MenuFlags flags, const char *initial, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, textLength, callbackFn); + auto item = new TextMenuItem(&reserve->getInfo()->anyInfo, initial, textLength, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder& TcMenuBuilder::textCustomRt(menuid_t id, const char *name, EepromPosition eepromPosition, + uint16_t textLength, RuntimeRenderingFn renderFn, MenuFlags flags, + const char *initial, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, eepromPosition, textLength, callbackFn); + auto item = new TextMenuItem(&reserve->getInfo()->anyInfo, renderFn, initial, textLength, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + return *this; +} + +TcMenuBuilder TcMenuBuilder::subMenu(menuid_t id, const char *name, MenuFlags flags, MenuCallbackFn callbackFn) { + AnyInfoReserve* reserve = fillInAnyInfo(id, name, 0xFFFF, 0, callbackFn); + auto item = new SubMenuItem(&reserve->getInfo()->subInfo, nullptr, nullptr, false); + flags.setOnMenuItem(item); + putAtEndOfSub(item); + auto back = new BackMenuItem(&reserve->getInfo()->subInfo, nullptr, false); + auto newBuilder = TcMenuBuilder(item, this); + newBuilder.putAtEndOfSub(back); + return newBuilder; +} diff --git a/src/TcMenuBuilder.h b/src/TcMenuBuilder.h new file mode 100644 index 00000000..5b9e42cd --- /dev/null +++ b/src/TcMenuBuilder.h @@ -0,0 +1,749 @@ + +#ifndef TCLIBRARYDEV_TCMENUBUILDER_H +#define TCLIBRARYDEV_TCMENUBUILDER_H +#include "EditableLargeNumberMenuItem.h" +#include "EepromAbstraction.h" +#include "MenuItems.h" +#include "RuntimeMenuItem.h" + +class LargeFixedNumber; +class TcMenuBuilder; +struct RgbColor32; +class MenuManager; +class ScrollChoiceMenuItem; + +#define INT_MENU_FLAG_READ 0x01 +#define INT_MENU_FLAG_HIDE 0x02 +#define INT_MENU_FLAG_LOCAL 0x04 +#define INT_MENU_FLAG_SECURE 0x08 + +// This definition is used when dynamic eeprom storage is enabled to indicate save to rom. +#define ROM_SAVE 0x0020 +// This definition is used when dynamic eeprom storage is enabled to indicate do not save to eeprom. +#define DONT_SAVE 0xFFFF + +/** + * This provides the extra flags for a menu item, such as its visibility and read only status. If you don't want to + * provide any of these flags, you can use the NoMenuFlags constant. + * + * Examples: + * @code + * builder.actionItem(myId, "Action", ROM_SAVE, NoMenuFlags) + * builder.actionItem(myId, "Action", ROM_SAVE, MenuFlags().readOnly().hide()) + * @endcode + */ +class MenuFlags { + uint8_t flags = 0; +public: + MenuFlags() = default; + MenuFlags(const MenuFlags&) = default; + MenuFlags& operator=(const MenuFlags&) = default; + ~MenuFlags() = default; + + MenuFlags& readOnly() { flags |= INT_MENU_FLAG_READ; return *this; } + MenuFlags& hide() { flags |= INT_MENU_FLAG_HIDE; return *this; } + MenuFlags& localOnly() { flags |= INT_MENU_FLAG_LOCAL; return *this; } + MenuFlags& securePin() { flags |= INT_MENU_FLAG_SECURE; return *this; } + + void setOnMenuItem(MenuItem* item) const; +}; + +/** + * Used when you have no flags such as readOnly, hide, or localOnly to set on the item. If you need to set any flags + * use MenuFlags().readOnly() for example. + */ +const MenuFlags NoMenuFlags; + +class AnyInfoReserve { + AllMenuInfoTypes info; +public: + AnyInfoReserve() : info() {} + AnyInfoReserve(const AnyInfoReserve&) = default; + AnyInfoReserve& operator=(const AnyInfoReserve&) = default; + ~AnyInfoReserve() = default; + menuid_t getKey() const { return info.anyInfo.id; } + bool isInUse() const { return info.anyInfo.id > 0;} + AllMenuInfoTypes* getInfo() { return &info; } +}; + +/** + * @class AnalogItemBuilder + * @brief A builder class for configuring and adding an analog menu item. + * + * This class facilitates the creation and customization of an analog menu item. It offers a fluent API to define + * attributes such as offset, divisor, step size, maximum value, and unit for the analog menu item before finalizing + * its addition to the parent menu structure. + * + * The builder modifies the `AnalogMenuInfo` properties and the associated `AnalogMenuItem` object + * to define the behavior and display characteristics of the item within the menu. The configured + * item is integrated into the menu hierarchy via the `commit` operation, returning control to the + * parent `TcMenuBuilder`. + * + * @see https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/analog-menu-item/ + */ +class AnalogItemBuilder { +private: + AnalogMenuItem& item; + AnalogMenuInfo& info; + TcMenuBuilder& parentBuilder; +public: + AnalogItemBuilder(AnalogMenuItem& item, AnalogMenuInfo& info,TcMenuBuilder& parentBuilder) : item(item), info(info), parentBuilder(parentBuilder) {} + AnalogItemBuilder(const AnalogItemBuilder &) = default; + + /** + * Set the offset of the analog item, the offset is added to the analog value before display. So if you set this + * to be a negative number, the displayed value will be negative until the raw value hits offset. + * @param offs the offset value to apply to the analog item + * @return a reference to the current instance of AnalogItemBuilder for chaining additional configurations + */ + AnalogItemBuilder& offset(int16_t offs) { info.offset = offs; return *this; } + /** + * Set the divisor of the analog item, I.E this is the fixed point divisor for the analog item. If you select for + * example 2, this will divide the analog value by 2 before displaying it in halves. + * @param divisor the divisor value + * @return a reference to the current instance of AnalogItemBuilder for chaining additional configurations + */ + AnalogItemBuilder& divisor(uint16_t divisor) { info.divisor = divisor == 0 ? 1 : divisor; return *this; } + /** + * An optional step size for the analog item, allowing for finer control over the displayed values. + * @param step the step size value + * @return a reference to the current instance of AnalogItemBuilder for chaining additional configurations + */ + AnalogItemBuilder& step(int step) { item.setStep(step); return *this; } + /** + * The maximum value that can be represented, this is the RAW value before divisors or offsets are applied. + * @param maxVal the maximum value that can be represented + * @return a reference to the current instance of AnalogItemBuilder for chaining additional configurations + */ + AnalogItemBuilder& maxValue(int maxVal) { info.maxValue = maxVal; return *this; } + + /** + * Set the unit name for the analog item, this is displayed after the value. + * @param unit the unit name to display + * @return a reference to the current instance of AnalogItemBuilder for chaining additional configurations + */ + AnalogItemBuilder& unit(const char* unit); + + /** + * @brief Finalizes the configuration of the analog menu item and returns control to the parent menu builder. + * + * This method completes the building process of the analog menu item by finalizing its configuration to the + * associated `AnalogMenuItem` and integrates it into the menu structure. It then returns control to the parent + * `TcMenuBuilder` to allow for further menu items. + * + * @return A reference to the parent `TcMenuBuilder` instance. + */ + TcMenuBuilder& endItem() const {return parentBuilder;} +}; + +class ScrollChoiceBuilder { +private: + AnyMenuInfo& info; + TcMenuBuilder& parentBuilder; + uint16_t initialValue; + MenuFlags menuFlags; + ScrollChoiceMenuItem* createdItem = nullptr; +public: + ScrollChoiceBuilder(AnyMenuInfo& info,TcMenuBuilder& parentBuilder, uint16_t initialValue, MenuFlags flags) : info(info), parentBuilder(parentBuilder), initialValue(initialValue), menuFlags(flags) {} + ScrollChoiceBuilder(const ScrollChoiceBuilder &) = default; + + /** + * @brief Configures the scroll choice menu item using a fixed array of choices stored in RAM. + * + * This method initializes and configures a `ScrollChoiceMenuItem` with a fixed array of choices + * located in RAM. + * + * @note If you use this method, you must set an EEPROM in `menuMgr` otherwise the item will not function correctly. + * + * The array is of fixed length, meaning that each item takes a fixed size. Example with + * fixedLen=8, numEntries=3: + * + * @code + * choiceString = "Option A Option B Option C " + * ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ + * Entry 0 Entry 1 Entry 2 + * (index) (index) (index) + * 0 8 16 + * @endcode + * + * Each entry occupies exactly fixedLen characters. If the text is shorter than fixedLen, + * it should be padded with spaces or zero terminated. Entry N starts at position (N * fixedLen). * + * List Docs - https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/list-menu-item/ + * ScrollChoice Docs - https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/scrollchoice-menu-item/ + * + * @param fixedArray A pointer to a fixed array of choices stored in RAM. + * @param numItems The total number of items available in the fixed array. + * @param fixedItemSize The size (in bytes) of each individual choice in the array. + * @return A reference to the current `ScrollChoiceBuilder` instance for method chaining. + */ + ScrollChoiceBuilder& fromRamChoices(const char* fixedArray, int numItems, int fixedItemSize); + + /** + * @brief Configures the scroll choice menu item using a fixed array of choices stored in EEPROM. + * + * This method initializes and configures a `ScrollChoiceMenuItem` with a fixed array of choices + * located in EEPROM. + * + * If you use this method, you must set an EEPROM in `menuMgr` otherwise the item will not function correctly. + * + * The array is of fixed length, meaning that each item takes a fixed size. Example with + * fixedLen=8, numEntries=3: + * + * @code + * choiceString = "Option A Option B Option C " + * ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ + * Entry 0 Entry 1 Entry 2 + * (index) (index) (index) + * 0 8 16 + * @endcode + * + * Each entry occupies exactly fixedLen characters. If the text is shorter than fixedLen, + * it should be padded with spaces or zero terminated. Entry N starts at position (N * fixedLen). + * + * List Docs - https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/list-menu-item/ + * ScrollChoice Docs - https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/scrollchoice-menu-item/ + * + * @param eepromPosition offset into the eeprom of the fixed array + * @param numItems The total number of items available in the fixed array. + * @param fixedItemSize The size (in bytes) of each individual choice in the array. + * @return A reference to the current `ScrollChoiceBuilder` instance for method chaining. + */ + ScrollChoiceBuilder& fromRomChoices(EepromPosition eepromPosition, int numItems, int fixedItemSize); + + ScrollChoiceBuilder& ofCustomRtFunction(RuntimeRenderingFn rtRenderFn, int numItems); + + /** + * Reading from EEPROM is slow, you can optionally cache the values in RAM once loaded, + * improving read performance. + * @return A reference to the current `ScrollChoiceBuilder` instance for method chaining. + */ + ScrollChoiceBuilder& cachingEepromValues(); + + /** + * @brief Finalizes the configuration of the scroll choice menu item and returns control to the parent menu builder. + * + * This method completes the building process of the menu item by finalizing its configuration to the + * associated `ScrollChoiceMenuItem` and integrates it into the menu structure. It then returns control to + * the parent `TcMenuBuilder` to allow for further menu items. + * + * @return A reference to the parent `TcMenuBuilder` instance. + */ + TcMenuBuilder& endItem() const; +}; + +/** + * @class TcMenuBuilder + * @brief A builder class to create and configure a hierarchical menu structure. + * + * This class provides a fluent API to define and organize menu items such as actions, text items, + * numeric items, submenus, and other specialized items. It maintains a tree-like structure of menu components, + * where each menu may have submenus or other related items. + * + * The class ensures the correct organization of menu items within containers using a parent-child hierarchy. + * It supports a wide range of menu item types, allowing for customization of behavior, storage, and rendering. + * + * @note Because menu creation happens at runtime, this builder cannot use PROGMEM storage for menu data, + * resulting in increased RAM memory usage compared to compile-time menu definition approaches. All menu + * structures, strings, and metadata must be allocated in RAM. For most menus, even on large AVR boards such as MEGA2560 + * the difference is negligible. However, you can still use the legacy static way if your case requires it. + * + */ +class TcMenuBuilder { +public: + explicit TcMenuBuilder(SubMenuItem* root, TcMenuBuilder* parent = nullptr) : currentSub(root), parent(parent) {} + TcMenuBuilder(const TcMenuBuilder &) = default; + TcMenuBuilder& operator=(const TcMenuBuilder &) = default; + ~TcMenuBuilder() = default; + + /** + * This is now the recommended way to store menu structures in EEPROM. It allows for dynamic + * menu configurations without the need for compile-time menu definitions, which can be + * beneficial for applications with changing menu requirements or frequent updates. If you use this mode to enable + * eeprom storage for an item simply use "ROM_SAVE" or "DONT_SAVE" in the eeprom position. + */ + TcMenuBuilder& usingDynamicEEPROMStorage(); + + /** + * @brief Adds a float menu item to the current menu structure. + * + * Float items in TcMenu are read only, for editable numeric items see "analogBuilder" and "largeNumberItem". + * + * This method configures and adds a floating-point menu item with specified properties, such as + * the number of decimal places and an initial value. The float item is integrated within the + * parent menu hierarchy during the build process. Additional behavior and display attributes + * can be controlled using the provided flags and callback function. + * + * @see https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/float-menu-item/ + * + * @param id The unique identifier for the menu item. + * @param name The display name of the menu item. + * @param eepromPosition for dynamic set to ROM_SAVE or DONT_SAVE, for legacy mode use an eeprom address. + * @param decimalPlaces The number of decimal places to display for the float value. + * @param flags Flags to control specific display and behavior properties of the menu item. + * @param initial The initial float value of the menu item. + * @param callbackFn A callback function that is invoked when the menu item is interacted with. + * @return Reference to the current instance of TcMenuBuilder to allow chaining additional builders. + */ + TcMenuBuilder& floatItem(menuid_t id, const char *name, EepromPosition eepromPosition, uint16_t decimalPlaces, MenuFlags flags, float initial = 0.0F, MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Adds an action menu item to the current menu structure. + * + * Action items are menu items that trigger a callback function when selected. They are typically used for + * executing specific actions or commands within the menu system. + * + * @see https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/action-menu-item/ + * + * @param id The unique identifier for the menu item. + * @param name The display name of the menu item. + * @param flags Flags to control specific display and behavior properties of the menu item. + * @param callbackFn A callback function that is invoked when the menu item is interacted with. + * @return Reference to the current instance of TcMenuBuilder to allow chaining additional builders. + */ + TcMenuBuilder& actionItem(menuid_t id, const char *name, MenuFlags flags, MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Creates and configures a boolean menu item to be added to the menu structure. + * + * This method allows the user to define the attributes of a boolean menu item, including its identifier, display name, + * EEPROM storage position, naming convention, flags, initial state, and callback function. The resulting menu item is + * integrated into the menu system and linked to its parent container. + * + * @see https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/boolean-menu-item/ + * + * @param id The unique identifier for the menu item. + * @param name The display name of the menu item. + * @param eepromPosition for dynamic set to ROM_SAVE or DONT_SAVE, for legacy mode use an eeprom address. + * @param naming The naming convention to use for boolean states (e.g., ON/OFF, YES/NO). + * @param flags The menu flags that define item properties and behaviors. + * @param initial The initial state of the boolean item (true or false). + * @param callbackFn The function to be called when the item's value changes. + * @return A reference to the `TcMenuBuilder`, allowing for method chaining. + */ + TcMenuBuilder& boolItem(menuid_t id, const char *name, EepromPosition eepromPosition, BooleanNaming naming, MenuFlags flags, bool initial = false, MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Creates and configures an analog menu item as part of the menu structure. + * + * This method initializes an analog menu item with provided attributes such as ID, name, EEPROM position, + * flags, initial value, and a callback function. It adds the item to the current menu structure and returns + * an AnalogItemBuilder for further configuration. The pattern for using this is: + * + * @code + * builder.analogBuilder(myId, "Analog1", ROM_SAVE, NoMenuFlags) + * .offset(10) + * .divisor(100) + * .step(1) + * .maxValue(100) + * .unit("V") + * .endItem() // <<== return back to building menu items + * boolItem(....) + * @endcode + * + * @see https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/analog-menu-item/ + * + * + * @param id The unique identifier for the analog menu item. + * @param name The display name for the menu item. + * @param eepromPosition for dynamic set to ROM_SAVE or DONT_SAVE, for legacy mode use an eeprom address. + * @param flags Configuration flags defining the item's behavior and properties. + * @param initialValue The initial value of the analog item. + * @param callbackFn The callback function invoked when the menu item value changes. + * @return AnalogItemBuilder to configure the analog item, call "endItem()" once done to finalize and return to menu building. + */ + AnalogItemBuilder analogBuilder(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, uint16_t initialValue = 0, MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Adds an enumerated menu item to the menu structure. + * + * This method allows the creation of an enumerated menu item, where the user can select one value from + * a predefined list of options. The options are represented as an array of string identifiers, providing + * a set number of choices for the menu item. + * + * @see https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/enum-menu-item/ + * + * In the example we first create an array globally (IE outside any function), then we use that array later to build + * the menu item: + * + * @code + * // 1. globally define a array of strings + * const char enumArray[] = { "Hello", "World" }; + * // 2. use the array to create an item + * tcMenuBuilder.enumItem(myId, "Greeting", myPositionInRom, enumArray, 2, 0, nullptr); + * @endcode + * + * @param id The unique identifier for the menu item. + * @param name The text label of the menu item, displayed in the menu interface. + * @param eepromPosition for dynamic set to ROM_SAVE or DONT_SAVE, for legacy mode use an eeprom address. + * @param enumEntries An array of strings representing the enumeration choices for the menu item. + * @param numEntries The total number of entries in the enumeration array. + * @param flags The configuration flags for the menu item, specifying additional behaviors or properties. + * @param value The default or initial selected value of the enumerated menu item, represented as an index. + * @param callbackFn The function to call when the menu item is selected or its value changes. + * @return A reference to the current instance of TcMenuBuilder for chaining additional menu item declarations*/ + TcMenuBuilder& enumItem(menuid_t id, const char *name, EepromPosition eepromPosition, const char **enumEntries, + uint16_t numEntries, MenuFlags flags, uint16_t value = 0, + MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Creates a submenu item with the specified attributes and integrates it into the menu hierarchy. + * + * This method allows the creation of a submenu within the current menu structure by providing its unique ID, name, flags, + * and callback function. A back menu item will also be added, this becomes the title for the submenu when it is on + * display, and so it is the first item in the submenu. + * + * Once you call this method, you're in a new stacked MenuBuilder that is building for that submenu. To exit + * this submenu you call `endSub` which takes you back to the previous level. + * + * @param id The unique identifier for the submenu item. + * @param name The display name of the submenu item. + * @param flags Flags that specify the attributes and behavior settings for the submenu item. + * @param callbackFn A function pointer for the callback executed when the submenu item is activated. + * @return A new instance of `TcMenuBuilder` that represents the new submenu item, allowing for further configuration or chaining operations. + */ + TcMenuBuilder subMenu(menuid_t id, const char *name, MenuFlags flags, MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Adds a text menu item to the menu structure. + * + * This method creates and configures a text menu item with the specified parameters. The text item is used + * to store and edit textual data in the menu. The method allocates the necessary resources and integrates + * the created item with the menu hierarchy. + * + * https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/editabletext-menu-item/ + * + * @param id The unique identifier of the menu item. + * @param name The display name of the menu item. + * @param eepromPosition for dynamic set to ROM_SAVE or DONT_SAVE, for legacy mode use an eeprom address. + * @param textLength The maximum length of the text that the item can hold. + * @param flags The configuration flags that define the item's behavior and state. + * @param initial The initial text value assigned to the menu item. + * @param callbackFn The callback function triggered when the item's value changes. + * @return A reference to the current instance of `TcMenuBuilder` to enable method chaining. + */ + TcMenuBuilder& textItem(menuid_t id, const char *name, EepromPosition eepromPosition, uint16_t textLength, + MenuFlags flags, const char *initial = "", MenuCallbackFn callbackFn = nullptr); + + /** + * Advanced build option for override of the regular text component for advanced cases, for example editing values that + * need customization such as editing hex values for example. + * @param id the ID of the item + * @param name the name of the item + * @param eepromPosition for dynamic set to ROM_SAVE or DONT_SAVE, for legacy mode use an eeprom address. + * @param textLength The length of the text to be edited. + * @param renderFn The callback function that will customize the control. Consult documentation for details. + * @param flags The configuration flags that define the item's behavior and state. + * @param initial the initial value, optional. + * @param callbackFn The callback function triggered when the item's value changes. + * @return + */ + TcMenuBuilder& textCustomRt(menuid_t id, const char *name, EepromPosition eepromPosition, uint16_t textLength, + RuntimeRenderingFn renderFn, MenuFlags flags, const char* initial = "", MenuCallbackFn callbackFn = nullptr); + + /** + * Adds an IP address menu item to the menu structure. This item allows the user + * to interact with and configure an IP address. + * + * @param id The unique identifier for this menu item. + * @param name The display name for this menu item. + * @param eepromPosition for dynamic set to ROM_SAVE or DONT_SAVE, for legacy mode use an eeprom address. + * @param flags The configuration flags that define the item's behavior and state. + * @param callbackFn An optional callback function triggered when the menu item is changed. + * @return Reference to the TcMenuBuilder instance to allow for method chaining. + */ + TcMenuBuilder& ipAddressItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, MenuCallbackFn callbackFn = nullptr); + + /** + * Adds an IP Address type menu item to the menu structure being built. The IP Address menu item allows users + * to configure or display an IP address directly within the menu. + * + * @param id The unique identifier for the menu item. + * @param name The display name for the menu item. + * @param eepromPosition The EEPROM storage position for persisting the value, or -1 if not stored in EEPROM. + * @param flags The flags specifying visibility, read-only status, and other properties of the menu item. + * @param ipInitial The initial value for the IP address storage. + * @param callbackFn The callback function invoked when the menu item is selected or updated. + * @return Reference to the current instance of TcMenuBuilder to allow method chaining. + */ + TcMenuBuilder& ipAddressItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, + IpAddressStorage ipInitial, MenuCallbackFn callbackFn = nullptr); + + /** + * Advanced construction/build option. Adds a custom IP address menu item to the menu using the provided parameters. + * This method allows customization of properties such as the menu ID, display name, EEPROM storage position, flags, + * initial IP address, and an optional + * callback function. + * + * @param id The unique identifier for the menu item. + * @param name The display name of the menu item. + * @param eepromPosition The EEPROM storage position for the value of this item. + * @param flags Additional menu flags controlling visibility, read-only status, etc. + * @param renderFn The callback function that will customize the control. Consult documentation for details. + * @param ipInitial The initial value for the IP address to be displayed or stored. + * @param callbackFn (Optional) A callback function invoked when the menu item is interacted with. Defaults to nullptr if not provided. + * @return A reference to the TcMenuBuilder for further modification or chaining of method calls. + */ + TcMenuBuilder& ipAddressCustomRt(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, + RuntimeRenderingFn renderFn, IpAddressStorage ipInitial, MenuCallbackFn callbackFn = nullptr); + + TcMenuBuilder& timeItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, MultiEditWireType timeFormat, + const TimeStorage& timeStorage, MenuCallbackFn callbackFn = nullptr); + + TcMenuBuilder& timeItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, MultiEditWireType timeFormat, + MenuCallbackFn callbackFn = nullptr); + + TcMenuBuilder& timeItemCustomRt(menuid_t id, const char *name, EepromPosition eepromPosition, const TimeStorage& timeStorage, + RuntimeRenderingFn renderFn, MenuFlags flags, MultiEditWireType timeFormat, MenuCallbackFn callbackFn = nullptr); + + TcMenuBuilder& dateItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, DateStorage initial, + MenuCallbackFn callbackFn = nullptr); + + TcMenuBuilder& dateItem(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, MenuCallbackFn callbackFn = nullptr); + + TcMenuBuilder& dateItemCustomRt(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, DateStorage initial, + RuntimeRenderingFn renderFn, MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Creates and preconfigures a ScrollChoiceBuilder to define a scrollable choice menu item. + * + * This method facilitates the setup of a scrollable choice menu item that allows the user to select + * a value from a list of predefined choices. The ScrollChoiceBuilder returned by this method enables + * further customization before the item is finalized and integrated into the menu structure. When you've finished + * configuring it, make sure you call `endItem()` to finalize the menu item and add it to the menu structure. + * + * @code + * builder.scrollChoiceBuilder(myId, "Choice Menu", myRomLocation, NoMenuFlags) + * .fromRomChoices(romArrayLocation, numItems, fixedItemSize); + * .cachingEepromValues() + * .endItem(); <<== return back to building menu items + * .boolItem(...) + * @endcode + * + * @param id The unique identifier for the menu item. + * @param name The display name for the menu item. + * @param eepromPosition for dynamic set to ROM_SAVE or DONT_SAVE, for legacy mode use an eeprom address. + * @param flags Flag options defining the item's behavior and characteristics. + * @param initial The index of the initial choice to display (default is 0). + * @param callbackFn The callback function to be triggered on value changes (default is nullptr). + * @return ScrollChoiceBuilder to configure the item, call "commit()" once done to finalize and return to menu building. + */ + ScrollChoiceBuilder scrollChoiceBuilder(menuid_t id, const char *name, EepromPosition eepromPosition, MenuFlags flags, uint16_t initial = 0, MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Configures and adds a 32-bit RGB menu item to the menu structure. + * + * This method enables the creation of a menu item that represents a 32-bit RGB color value. The color can optionally + * include an alpha channel for transparency. The item properties, such as identifier, display name, EEPROM storage position, + * and associated flags, are defined during configuration. The color item's state can be modified or retrieved via callback functions. + * + * @param id The unique menu identifier for this item. + * @param name The display name of the menu item. + * @param eepromPosition for dynamic set to ROM_SAVE or DONT_SAVE, for legacy mode use an eeprom address. + * @param alphaChannel Indicates if an alpha channel (transparency) is included in the RGB color representation. + * @param flags Custom attributes or behaviors associated with the menu item, defined using the MenuFlags type. + * @param callbackFn A function callback invoked during menu value changes or actions. + * @return A reference to the current instance of the `TcMenuBuilder` for further configuration. + */ + TcMenuBuilder& rgb32Item(menuid_t id, const char *name, EepromPosition eepromPosition, bool alphaChannel, + MenuFlags flags, MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Adds an RGB32 menu item to the current menu being built. + * + * This method allows for the creation and integration of a 32-bit RGB color menu item into + * the menu hierarchy. The item supports optional alpha channel functionality and can be + * initialized with a provided starting color value. Configurable flags and a callback function + * define the behavior and interactions of the item. The method uses the fluent interface pattern, + * returning a reference to the current `TcMenuBuilder` instance for chaining further menu additions. + * + * @param id The unique identifier for the RGB32 menu item. + * @param name The display name of the menu item. + * @param eepromPosition for dynamic set to ROM_SAVE or DONT_SAVE, for legacy mode use an eeprom address. + * @param alphaChannel Boolean flag indicating whether the alpha channel is supported. + * @param flags Additional configuration flags for the menu item. + * @param initial The initial color value, for example, `RgbColor32(255, 0, 0)` for red. + * @param callbackFn A function pointer for the menu item callback, invoked on user interaction. + * @return A reference to the current `TcMenuBuilder` instance to allow for method chaining. + */ + TcMenuBuilder& rgb32Item(menuid_t id, const char *name, EepromPosition eepromPosition, bool alphaChannel, + MenuFlags flags, const RgbColor32& initial, MenuCallbackFn callbackFn = nullptr); + + /** + * Advanced construction/build case for RGB items where you need to override the menu in a custom way. This is + * normally used when you want to customize the rendering or behavior of the RGB menu item beyond the standard options. + * + * @param id The unique identifier for the RGB32 menu item. + * @param name The display name of the menu item. + * @param eepromPosition for dynamic set to ROM_SAVE or DONT_SAVE, for legacy mode use an eeprom address. + * @param alphaChannel Boolean flag indicating whether the alpha channel is supported. + * @param renderFn The custom rendering function for the RGB menu item. Consult the documentation + * @param flags Additional configuration flags for the menu item. + * @param initial The initial color value of type `RgbColor32` for the menu item. + * @param callbackFn A function pointer for the menu item callback, invoked on user interaction. + * @return A reference to the current `TcMenuBuilder` instance to allow for method chaining. + */ + TcMenuBuilder& rgb32CustomRt(menuid_t id, const char *name, EepromPosition eepromPosition, bool alphaChannel, + RuntimeRenderingFn renderFn, MenuFlags flags, const RgbColor32& initial, MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Adds a list menu item to the menu structure with content stored in RAM. + * + * This method creates a runtime-configurable list menu item, where the items in the list and their metadata + * are stored in RAM. The list menu item is added as a child to the current submenu. + * List Docs - https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/list-menu-item/ + + * @param id The unique identifier for the menu item. + * @param name The name of the menu item, displayed in the menu. + * @param numberOfRows The total number of rows (entries) in the list. + * @param arrayOfItems An array of C-strings containing the list entries. These must be stored in RAM. + * @param flags Additional configuration flags for the menu item. + * @param callbackFn A callback function invoked when interaction with the menu item occurs. + * @return A reference to the current TcMenuBuilder, allowing fluent-style chaining. + */ + TcMenuBuilder& listItemRam(menuid_t id, const char *name, uint16_t numberOfRows, const char** arrayOfItems, MenuFlags flags, MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Adds a list menu item to the menu structure with content stored in FLASH. + * + * This method creates a runtime-configurable list menu item, where the items in the list items are stored in FLASH. + * The list menu item is added as a child to the current submenu. + * List Docs - https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/list-menu-item/ + * + * @param id The unique identifier for the menu item. + * @param name The name of the menu item, displayed in the menu. + * @param numberOfRows The total number of rows (entries) in the list. + * @param arrayOfItems An array of C-strings containing the list entries. These must be stored in FLASH. + * @param flags Additional configuration flags for the menu item. + * @param callbackFn A callback function invoked when interaction with the menu item occurs. + * @return A reference to the current TcMenuBuilder, allowing fluent-style chaining. + */ + TcMenuBuilder& listItemFlash(menuid_t id, const char *name, uint16_t numberOfRows, const char** arrayOfItems, MenuFlags flags, MenuCallbackFn callbackFn = nullptr); + + /** + * @brief Adds a runtime custom list item to the menu structure. + * + * This method allows the addition of a list item that uses a custom runtime rendering function + * to dynamically determine its content. The item is read-only by default and can display multiple rows of data. + * List Docs - https://tcmenu.github.io/documentation/arduino-libraries/tc-menu/menu-item-types/list-menu-item/ + * + * @param id The unique ID of the menu item. + * @param name The name (or label) of the menu item. + * @param numberOfRows The number of rows that the list item can display. + * @param flags Additional configuration flags for the menu item. + * @param rtRenderFn The function used to render the list item's contents dynamically at runtime. + * @param callbackFn The callback function that is invoked when the item is interacted with. + * @return A reference to the current `TcMenuBuilder` instance, allowing method chaining. + */ + TcMenuBuilder& listItemRtCustom(menuid_t id, const char *name, uint16_t numberOfRows, RuntimeRenderingFn rtRenderFn, MenuFlags flags, MenuCallbackFn callbackFn = nullptr); + + /** + * Adds a large number menu item to the menu structure. This is used for numeric values with a significant number of digits, + * including fractional parts, such as financial or scientific fields. The total and fractional places control the format + * and precision of the number. + * + * When you edit a large number, the editing occurs one digit at a time, allowing for precise control over the number's value, + * even when there's a large number of digits. + * + * @param id The unique identifier for the menu item. + * @param name The display name of the menu item. + * @param eepromPosition The EEPROM position for storing the large number value. + * @param num The large fixed number, for example, `LargeFixedNumber(10, 4, 98765, 1234)` is 10.4 digits representing "98765.1234". + * @param allowNegative Whether negative numbers are allowed in the large number. + * @param flags Additional flags to control visibility, read-only status, or other properties of the menu item. + * @param callbackFn An optional callback function that is triggered when the value changes. + * @return A reference to the builder, allowing method chaining. + */ + TcMenuBuilder& largeNumberItem(menuid_t id, const char *name, EepromPosition eepromPosition, const LargeFixedNumber& num, + bool allowNegative, MenuFlags flags, MenuCallbackFn callbackFn = nullptr); + + /** + * Advanced construction case, adds a customized large number menu item with a renderFn callback to specialize how + * it is displayed and processed. For regular large numbers use the standard method "largeNumber". + * + * @param id The unique identifier for the menu item. + * @param name The display name of the menu item. + * @param eepromPosition The EEPROM position for storing the large number value. + * @param num The large fixed number, for example, `LargeFixedNumber(10, 4, 98765, 1234)` is 10.4 digits representing "98765.1234". + * @param allowNegative Whether negative numbers are allowed in the large number. + * @param renderFn The function used to render the list item's contents dynamically at runtime. + * @param flags Additional flags to control visibility, read-only status, or other properties of the menu item. + * @param callbackFn An optional callback function that is triggered when the value changes. + * @return A reference to the builder, allowing method chaining. + */ + TcMenuBuilder& largeNumberRtCustom(menuid_t id, const char *name, EepromPosition eepromPosition, const LargeFixedNumber& num, + bool allowNegative, RuntimeRenderingFn renderFn, MenuFlags flags, MenuCallbackFn callbackFn = nullptr); + + /** + * Add an item to the menu that allows for authentication using EEPROM storage. This item is read-only and displays + * authentication status. When the authentication status changes, the provided callback function is invoked. + * + * @param id The unique ID of the menu item. + * @param name The name (or label) of the menu item. + * @param flags Menu flags to configure the item's behavior. + * @param onAuthChanged Optional callback function invoked when authentication status changes. + * @return A reference to the current `TcMenuBuilder` instance, allowing method chaining. + */ + TcMenuBuilder& eepromAuthenticationItem(menuid_t id, const char* name, MenuFlags flags, MenuCallbackFn onAuthChanged = nullptr); + + /** + * Add an item to the menu that provides remote connectivity monitoring. This item is read-only and displays + * connectivity status. + * + * @param id The unique ID of the menu item. + * @param name The name (or label) of the menu item. + * @param flags Menu flags to configure the item's behavior. + * @return A reference to the current `TcMenuBuilder` instance, allowing method chaining. + */ + TcMenuBuilder& remoteConnectivityMonitor(menuid_t id, const char* name, MenuFlags flags); + + /** + * Add an item that you've created manually, such as a custom item outside the scope of this builder. For example, if + * you had used the traditional static method for some complex items, you could add them using this method. + * @param itemToAdd the item to append to the menu hierarchy. The item must not be deallocated after addition! + * @return A reference to the current TcMenuBuilder instance for method chaining. + */ + TcMenuBuilder& appendCustomItem(MenuItem* itemToAdd); + + /** + * @brief Ends the current submenu context and returns to the parent menu. + * + * This method exits the current submenu level and transitions back to the parent menu context. + * If the current menu has no parent, it returns a reference to the root TcMenuBuilder instance. + * This allows chaining and continuation of the menu-building process. + * + * @return A reference to the parent TcMenuBuilder instance or the root builder instance if no parent exists. + */ + TcMenuBuilder& endSub(); + + /** + * @brief Fills in an AnyInfoReserve structure with the provided menu item information. + * + * This method reserves and populates an AnyInfoReserve object with the specified menu item parameters. + * It is typically used internally by the builder to prepare menu item metadata before creating and registering + * a new menu item in the hierarchy. It is probably only useful along with custom menu item creation. + * + * If you're creating custom menu items, then you can use this method to set up the + * necessary `info` metadata for your custom item before adding it to the menu hierarchy. + * + * @param id The unique identifier for the menu item. + * @param name The display name of the menu item. + * @param eeprom The EEPROM position for persisting the menu item's state, or -1 if not using EEPROM. + * @param maxVal The maximum value for the menu item (interpretation depends on item type). + * @param callback_fn A callback function that will be triggered when the menu item is interacted with, or nullptr if no callback is needed. + * @return A pointer to an AnyInfoReserve object containing the populated menu item information. + */ + AnyInfoReserve* fillInAnyInfo(menuid_t id, const char *name, int eeprom, int maxVal, MenuCallbackFn callback_fn); + + void putAtEndOfSub(MenuItem * toAdd) const; + + MenuItem * getRootItem() const { + return currentSub->getChild(); + } + +private: + SubMenuItem* currentSub; + TcMenuBuilder* parent; + +}; + +#endif //TCLIBRARYDEV_TCMENUBUILDER_H diff --git a/src/tcMenu.h b/src/tcMenu.h index b7045477..e8f8f51f 100755 --- a/src/tcMenu.h +++ b/src/tcMenu.h @@ -510,6 +510,7 @@ class MenuManager { * @param item the item to become the active edit, the right submenu should have already been enabled first */ void setupForEditing(MenuItem* item); + protected: void actionOnCurrentItem(MenuItem * toEdit); void actionOnSubMenu(MenuItem* nextSub);