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); 

Title item from 2.1 onwards

From 2.1 onwards the title item is another menu item that renders differently to regular items. It is selectable and can runs a callback when clicked, just like an action item does. For example, we often use this to display the application and version information.

void myTitleCallback(int id) {
    // title has been clicked at this point
}

// This sets the callback to be used when the title is clicked.
setTitlePressedCallback(myTitleCallback);

You can also use an inline lambda function instead of declaring the function separately as above. The ID of the title is always 0, which is the ROOT ID.

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

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

To navigate to a new menu structure

navigateToMenu(theNewItem, possibleActive = nullptr, customMenu = false);

To pop the last menu off the stack (or to root if popBackToRoot is true)

resetMenu(popBackToRoot)

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

This site uses cookies to analyse traffic, serve ads by Google AdSense (non-personalized in EEA/UK), and to record consent. We also embed Twitter, Youtube and Disqus content on some pages, these companies have their own privacy policies.

Our privacy policy applies to all pages on our site

Should you need further guidance on how to proceed: External link for information about cookie management.