By dave | October 2, 2020

MenuManager contains the functions to manage menu items, there is a global instance of this class called menuMgr. Here we present the most commonly used features, check out the reference docs for more details. There are a lot of iteration helper functions that can be used to navigate through menu structures. These are described further down the page. The following two reference documentation pages extend on the information here:

Enabling next and back functionality

To enable next, back or both, you provide the pin onto which you’ve connected the button for this function. It will essentially register additional buttons with switches on your behalf. Read through the guide for your input plugin if in doubt how it configured switches.

menuMgr.setNextButton(pinid_t pin);
menuMgr.setBackButton(pinid_t pin);  

Controlling the menu items manually

If you need custom control over menu manager input, then you can directly call into the menu manager as if the encoder has changed value, the button has been pressed, or even acting as if back or next were pressed. Note that this is an advanced feature and although this is possible, it’s often better to work with the RotaryEncoder or switches when possible.

// usually called by the encoder to indicate the current encoder position
void valueChanged(int value); 
// called to indicate that the select button was pressed or help
void onMenuSelect(bool held); 
// called when the back or next button is pressed
void performDirectionMove(bool dirIsBack); 

Working with menu items

Menu Manager makes it very easy to save values to EEPROM between runs. Each menu item can optionally have a storage point in the EEPROM area (-1 / 0xffff means not stored). Any items that have a valid EEPROM address will be persisted upon calling the save function on menuMgr, and similarly, will be read back by calling load.

In order to use any EEPROM functions, or to use EEPROM based choice menu items, you need to set up an EepromAbstraction and provide a pointer to menuMgr using setEepromRef. For details of how to create an EepromAbstraction see AVR and Arduino EEPROM example and I2C AT24 EEPROM example.

// before making any call to load or save, or before using ChoiceMenuItems
menuMgr.setEepromRef(eepromPtr);

void menuMgr.load(magicKey = 0xfade, firstRunCallback = NULL);
void menuMgr.save(magicKey = 0xfade);

Where:

  • eeprom is a reference to an EEPROMAbstraction see links above.
  • magicKey this value is stored in the first two bytes of the ROM and then read back before loading. If the values do not match, no loading takes place.
  • firstRunCallback is only called when the magic key does not match, to make any preparations needed.

Should you need to use EEPROM functions before initialisation, you can set the root menu item before initialise for this special case.

menuMgr.setRootMenu(rootItem);

A frequent question that is asked is ‘how should I call save to ensure that all menu item state is stored’. The best way to do this is to use a power loss detection circuit and call save in that call back. The second best is to have a timed function (maybe once every 10 minutes) along with a commit handler that detects changes. Never call save in the menu item callback, if using a rotary encoder that would break your EEPROM in days.

To retrieve the top most (first) menu item, or to get the currently active top menu item.

MenuItem* menuMgr.getRoot();
MenuItem* menuMgr.getCurrentMenu();

Building menu structures at runtime

You can create additional items at runtime, you can even create new submenus at runtime. Any item that extends from RuntimeMenuItem can easily be created on the fly.

To append a menu item immediately after an existing item you use the below function, if you are adding several at once, use the silent option to prevent displays and remotes refreshing more than once. If you use silent, then you must follow this with a call to notifyStructureChanged.

menuMgr.addMenuAfter(existingItemPtr, newItemPtr, silent);
menuMgr.notifyStructureChanged();

You can even change the local display to a menu structure completely defined at runtime by calling the following, but bear in mind these would not be remotely through the API. However, it is very handy for local security dialogs and other similar local only situations:

menuMgr.setCurrentMenu(MenuItem* firstItemPtr);

Listening for changes on menu manager

You can listen for menu editing and structural changes using by providing an object that implements MenuManagerObserver. You create an object that extends from that class and provide it addChangeNotification:

class MyObserver : public MenuManagerObserver {
public:
    virtual void structureHasChanged() {
        Serial.println("menu tree structure changed"); 
    }
    
    virtual bool menuEditStarting(MenuItem* item) {
        Serial.println("Editing started");
        return true; // allow editing to start
    }

    virtual void menuEditEnded(MenuItem* item) {
        Serial.println("Edit committed");
    } 
} myObserver;

void setup() {
    // ...
    menuMgr.addChangeNotification(&myObserver);
} 

Alternatively, use the commit callback function which takes the same signature as a menu item callback but is only called upon an item being committed:

void setItemCommittedHook(MenuCallbackFn commitCallback)

Note that either of the above methods use one of the callback spaces. There is a limited number (default 4) of callbacks that can be registered, see the reference docs for more information. Be aware most renderers and remote capabilities tend to use a slot each.

Initialising the menu manager

When using the designer, this code gets added automatically, this is to fully document the API.

We initialise the menu manager as shown below, where renderer is a pointer to a renderer (or an instance of NoRenderer), root is the very first menu item and then this is followed by the pin used for either switches or the rotary encoder. Remember that pinA of the encoder must be an interrupt capable pin. If you’re not using the designer, then the plugins for display and remote are available within the tcMenu code repo within the two plugin packages, or may be packaged with the embedded folder. Copy the required files into your sketch and make sure they are initialised.

menuMgr.initForEncoder(MenuRenderer* renderer, MenuItem* root, uint8_t encoderPinA, uint8_t encoderPinB, uint8_t encoderButton);
menuMgr.initForUpDownOk(MenuRenderer* renderer, MenuItem* root, uint8_t upPin, uint8_t downPin, uint8_t okPin);
menuMgr.initWithoutInput(MenuRenderer* renderer, MenuItem* root);

So for example to initialise for no local user interface. Define a global variable:

NoRenderer noRenderer;

Now initialise for no input:

menuMgr.initWithoutInput(&noRenderer, &rootMenuItem);

Iterating over menu items

Most of the useful functions to iterate over menu items are within the following reference documentation page, we only document the most popular ones here, so it is worth checking this page too:

This class allows you to go through a menu structure using a common iteration approach. The iterator is provided with a predicate filter, then each item is tested against the predicate, and any that match are returned as the next match. Let’s take an example that gets all items of type AnalogMenuItem:

    MenuItemTypePredicate intPredicate(MENUTYPE_INT_VALUE, TM_EXTRA_INCLUDE_SUBMENUS);
    MenuItemIterator iterator;
    iterator.setPredicate(&intPredicate);

    auto item = iterator.nextItem();
    while(item != nullptr) {
        Serial.println(item->getId());
        item = iterator.nextItem();
    };

The first parameter to the type predicate is one of the MenuType enumeration. The second parameter is a flag that tells the predicate which mode it’s working in, you can or together more than one choice:

// bit 0 represnts either regular or inverted mode (eg not a Analog item)
#define TM_REGULAR  0
#define TM_INVERTED  1

// bit 3 being on prevents entry into menus marked local only
#define TM_REGULAR_LOCAL_ONLY  8
#define TM_INVERTED_LOCAL_ONLY  9

// bit 4 being on tells the predicate to always recurse into submenus
#define TM_EXTRA_INCLUDE_SUBMENUS 16

There is another ready-made predicate that filters by remote number and changes. This is useful if you are implementing a remote facility.

RemoteNoMenuItemPredicate(int remoteNumber); 

You can make your own predicate by extending from MenuItemPredicate

Getting specific menu items

Get a menu item by ID

MenuItem* getMenuItemById(int id);

Get the parent for a given item

MenuItem* getParentRoot(MenuItem* current);    

Increasing menu depth

Menu iteration uses an internal queue. To save memory on the device this is limited to 4 items in depth. EG Root -> SubMenu -> SubMenu -> SubMenu. If you need deeper trees than this, you can redefine MAX_MENU_DEPTH to any value at the cost of a little memory.

Back to tcMenu main page

Other pages within this category

comments powered by Disqus

We use cookies to analyse traffic and to personalise content. We also embed Twitter, Youtube and Disqus content on some pages, these companies have their own privacy policies.

Please see our privacy policy should you need more information or wish to adjust your settings.

Send a message
X

This message will be securely transmitted to Nutricherry LTD servers.