Simple Menu Class

Several posts have come up over the last few months looking for a good menu and UI system using an LCD. I have a good system that I have been recycling through project after project, but it was a bit cumbersome so I didn’t share it. But now I am moving the code to yet another project and I decided I would clean it up a bit and share my take on menus.

I like my code to be portable and hardware agnostic, so I pulled everything out into a few different headers. I plan to share the full implementation with rotary encoder, button and LCD soon. But first I wanted to share the menu system as it could be useful in all sorts of projects.

Since I don’t always know how my menu will be navigated and I certainly don’t know how you might have your buttons arranged, I made my MenuClass virtual. It requires you to derive a class from it and implement 3 or 4 methods. This way, the same menu structure can be used with all sorts of hardware. I’ll show an example implementation below. I kept it simple, so for the newbie this might be a good introduction to class inheritance and virtual functions. Or it can be hacked and pasted together to make a working menu without much knowledge of those things.

It works by calling a doMenu function which handles all the menu work. When a selection is made, this function instead calls a function pointer and runs whatever function is attached to that menu item. The function being called can (and should) be non-blocking. To facilitate this, the item functions all take no arguments and return boolean, true if they have finished and do not wish to be called again, or false if they are not complete and wish to be called again on the next pass. Blocking code can simply return true at the end of the function. doMenu will keep calling the selected function until it returns true.

The base class looks like this:

MenuClass.h

#ifndef MENUCLASS_H_
#define MENUCLASS_H_

#define USING_PROGMEM

#include "Arduino.h"

typedef boolean (*Item_Function)();

const typedef struct MenuItem_t {
 char text[16];
 Item_Function func;
} MenuItem;

class MenuList {
private:
 MenuItem *menuItems;
 uint8_t listSize;
public:
 MenuItem* getItem(int aIndex);
 MenuList(MenuItem* aList, uint8_t aSize) :
 menuItems(aList), listSize(aSize) {
 }
 uint8_t getSize();

};

class MenuClass {
private:

 boolean cancelFlag;
 boolean runningFunction;
public:
 MenuList *currentMenu;
 int currentItemIndex;

 MenuClass();
 MenuClass(MenuList* aList);

 void doMenu();
 void setCurrentMenu(MenuList*);
 boolean runFunction();
 void getText(char*, int);
 virtual void displayMenu() = 0;
 virtual boolean checkForCancel();
 virtual int updateSelection() = 0;
 virtual boolean selectionMade() = 0;

};

#endif /* MENUCLASS_H_ */

MenuClass.cpp

#include "MenuClass.h"

MenuItem* MenuList::getItem(int aIndex) {
 // To make modulo work with negatives the way we want
 while (aIndex < 0) {
 aIndex += listSize;
 }
 return &(menuItems[aIndex % listSize]);
}

uint8_t MenuList::getSize() {
 return this->listSize;
}

MenuClass::MenuClass() {
 currentMenu = 0;
 currentItemIndex = 0;
 cancelFlag = false;
 runningFunction = false;
}

MenuClass::MenuClass(MenuList* aList) {
 currentMenu = aList;
 currentItemIndex = 0;
 cancelFlag = false;
 runningFunction = false;

}

boolean MenuClass::runFunction() {
 Item_Function theFunc;

#ifdef USING_PROGMEM
 theFunc =
 (Item_Function) pgm_read_word(&(currentMenu->getItem(currentItemIndex)->func));
#else
 theFunc = currentMenu->getItem(currentItemIndex)->func;

#endif
 return theFunc();

}

void MenuClass::doMenu() {
 cancelFlag = checkForCancel();
 if (runningFunction) {
 runningFunction = !runFunction();
 } else {
 //menuHandler();
 cancelFlag = false;
 int selUp = updateSelection();
 if (selUp) {
 currentItemIndex += selUp;
 //  Avoid negative numbers with the modulo
 //  to roll the menu over properly
 while (currentItemIndex < 0) {
 currentItemIndex += currentMenu->getSize();
 }
 currentItemIndex %= currentMenu->getSize();

 } else {   // only check for selection if menu not also updating
 if (selectionMade()) { // selection made, run item function once now
 if (!runFunction()) { // If item function returns false then it isn't done and we need to keep running it.
 runningFunction = true;
 return; // skip displaying the menu if we're going to run item function again.
 }
 }
 }
 displayMenu();
 }
}

void MenuClass::getText(char* aBuf, int aIndex) {
#ifdef USING_PROGMEM
 strcpy_P(aBuf, (currentMenu->getItem(aIndex)->text));
#else
 strcpy(aBuf, (currentMenu->getItem(aIndex)->text));
#endif
}

boolean MenuClass::checkForCancel() {
 return false;
}

void MenuClass::setCurrentMenu(MenuList* aMenu) {
 currentMenu = aMenu;
 currentItemIndex = 0;
}

The doMenu function is where the action is. It takes care of a few things that I’ve found are important in a menu. First, it won’t allow a selection on the same pass that the display changes. No more accidentally turning the knob as you push it and selecting something you didn’t mean to. It also handles a cancel flag that can be used by the functions called from the menu. This is optional in derived classes. And of course it handles calling the other functions that are called from the menu and returning control to the menu when they are complete.

Here is an example implementation. Please note that it is “roughed in”. My buttons are hardware debounced, so I left polling buttons and debouncing as exercises for the user. This implementation works, but serves only to illustrate the concept.

SimpleSerialMenu.ino:

#include <MenuClass.h>

boolean fun1();
boolean fun2();
boolean fun3();
boolean fun4();
boolean gotoMenuOne();
boolean gotoMenuTwo();

const int upButtonPin = 4;
const int downButtonPin = 5;
const int selectButtonPin = 6;


/*****************************
 ******************************
 * Define the new derived menu class
 * implement the functions to update 
 * the menu, make a selection, and
 * display the menu
 ******************************
 *****************************/

class SimpleSerialMenu: 
public MenuClass {

public:
  int updateSelection();
  boolean selectionMade();
  void displayMenu();

};

int SimpleSerialMenu::updateSelection() {
  // This is a simplified example
  // Edge detection and debouncing are exercises left to the user
  if(digitalRead(upButtonPin) == LOW){
    return 1;
  }
  if(digitalRead(downButtonPin) == LOW){
    return -1;
  }
  return 0;
}

boolean SimpleSerialMenu::selectionMade() {
  // This is a simplified example
  // Edge detection and debouncing are exercises left to the user
  return !digitalRead(selectButtonPin);  
}

void SimpleSerialMenu::displayMenu() {

  //  Blink WIthout Delay to keep prints from blasting out too fast. 
  static unsigned long preMil = millis();
  unsigned long curMil = millis();
  if(curMil - preMil >= 500) {  // print every half second
    preMil = curMil;

    char outBuf[17];

    for (int i = 0; i < currentMenu->getSize(); i++) {

      if (i == currentItemIndex) {
        Serial.print(F("---> "));
      }
      else {
        Serial.print(F("     "));
      }
      // use getText method to pull text out into a buffer you can print
      getText(outBuf, i);
      Serial.println(outBuf);
    }
    Serial.print(F("currentItemIndex =  "));
    Serial.println(currentItemIndex);
    for (int i = 0; i < 3; i++) {  // put some spaces before next print
      Serial.println();
    }
  }
}



/*****************************
 ******************************
 * Setup the menu as an array of MenuItem
 * Create a MenuList and an instance of your 
 * menu class
 ******************************
 *****************************/

MenuItem PROGMEM firstList[3] = { 
  { 
    "Item_one", fun1    }
  , { 
    "Item_two", fun2    }
  , { 
    "Menu_2", gotoMenuTwo    }
};

MenuItem PROGMEM secondList[3] = {
  {
    "Back" , gotoMenuOne   }
  ,{
    "Item_three" , fun3   }
  ,{
    "Item_four" , fun4   }
};

MenuList list1(firstList, 3);
MenuList list2(secondList, 3);

SimpleSerialMenu myMenu;



void setup(){
  pinMode(upButtonPin, INPUT_PULLUP);
  pinMode(downButtonPin, INPUT_PULLUP);
  pinMode(selectButtonPin, INPUT_PULLUP);

  Serial.begin(19200);
  myMenu.setCurrentMenu(&list1);

  Serial.println(F("Running Menu Test:"));
}

void loop(){
  myMenu.doMenu();

  //  Whatever other code you have.  
  //  Try to keep it non-blocking while the 
  //  menu is displayed  
  delay(250);  // because my buttons aren't debounced. 
}



/*****************************
 ******************************
 * Define the functions you want your menu to call
 * They can be blocking or non-blocking
 * They should take no arguments and return a boolean
 * true if the function is finished and doesn't want to run again
 * false if the function is not done and wants to be called again
 ******************************
 *****************************/


boolean fun1() {
  // blocking functions that simply return true will be run once and will complete
  //  before the code goes to the next loop
  Serial.println(F("FUN 1:  running right now and printing this long line"));
  delay(3000);
  return true;
}

boolean fun2() {
  Serial.println(F("FUN 2:  running right now and printing this long line"));
  delay(3000);
  return true;
}

boolean fun3() {
  Serial.println(F("FUN 3:  locked here until button push"));
  delay(100);
  if(digitalRead(selectButtonPin) == LOW){
    return true;  // signal finished if button pressed
  }
  else {
    return false;  // no button press, keep running
  }
}

boolean fun4() {
  // remember you have to have at least one case return true or you lock things up
  Serial.println(F("FUN 4:  Locked here forever"));
  delay(3000);
  return false;
}

boolean gotoMenuOne() {
  myMenu.setCurrentMenu(&list1);
  return true;
}

boolean gotoMenuTwo() {
  myMenu.setCurrentMenu(&list2);
  return true;
}

At the top of the sketch, we create a new derived class from MenuClass and we implement 3 functions that it requires.

updateSelection() should read your up and down buttons or whatever you have and figure out how much you want to change the currently active selection on the menu by. It should return that value as an int. Positive numbers to advance the menu, negative numbers to scroll backwards. You don’t need to know which selection is currently active, only by how much you want to change it. In simple terms go up if the up button is pushed, or down if the down button is pushed, or don’t change if neither button is pushed.

selectionMade() should read your select button or whatever method you have for selecting something from the menu. In this case, it just reads the select button and returns whether it is pressed or not.

displayMenu() this code should do just what it says. You have access to a variable currentItemIndex and a method called getText that will fill a buffer with the text from whatever item index you want. Write whatever you want here. The code here simply iterates through the items in the current menu and prints them to Serial. When it gets to the current selection, it also prints an arrow to indicate it. Your code can do whatever it wants but should NOT ever check the buttons or modify the currentItemIndex. All it should do is display the menu.

At the end of the example are the functions we’ll call from the menu. They all return boolean and some are designed to illustrate the concept of returning true when you are done and false to be called again. These functions can do whatever they want as long as they take no arguments and return a boolean. Be careful that at least one possibility to return true exists or the code will be locked on that menu selection.

Also we have functions to switch between the different menus. Any of these can be used more than once, so you should only need one of each of these no matter how convoluted your menu structure gets.

Finally there is the setup of the menu. This is the beautiful part. I really wanted this sort of syntax to make it easier to create and add to menus with minimal changes to other code.

MenuItem PROGMEM firstList[3] = { 
  { 
    "Item_one", fun1    }
  , { 
    "Item_two", fun2    }
  , { 
    "Menu_2", gotoMenuTwo    }
};

MenuItem PROGMEM secondList[3] = {
  {
    "Back" , gotoMenuOne   }
  ,{
    "Item_three" , fun3   }
  ,{
    "Item_four" , fun4   }
};

MenuList list1(firstList, 3);
MenuList list2(secondList, 3);

SimpleSerialMenu myMenu;

Creating the arrays of MenuItem seems rather self explanatory. Each set of brackets gets a text and a function pointer. They MUST go into PROGMEM unless you undefine the line in the MenuClass header. If you don’t know about progmem don’t worry, the getText function and doMenu functions handle everything but putting the word PROGMEM in this array definition.

Next you must create MenuList items for each array. This is a class for the MenuClass to work with. Just do it.

Finally, create an instance of your menu.

In setup, you need this line somewhere

myMenu.setCurrentMenu(&list1);

where list1 is whatever menu list you want your menu to start out on.

And loop has to call doMenu over and over to keep the menu going:

void loop(){
  myMenu.doMenu();

  //  Whatever other code you have.  
  //  Try to keep it non-blocking while the 
  //  menu is displayed  
  delay(250);  // because my buttons aren't debounced. 
}

I hope this is explained well enough. It really boils down to implementing 3 functions and writing the functions you want to call from your menu and then putting them together with text in arrays. Should be easy enough to hack and paste.

Here are those files attached:

EDIT: The .ino file has been updated re post #19 to address the issue of people getting errors about functions not being declared in scope. 30-DEC-2018

MenuClass.cpp (2.13 KB)

MenuClass.h (1.02 KB)

SimpleSerialMenu.ino (4.21 KB)

First of all thanks for sharing your code, This will be a great help for me to get an good understanding of how it works and progress with it from there, But I’m trying to print it to I2C LCD (4x20) but it displays stuff all over the place on the LCD I’ve tried this way

lcd.print(F("currentItemIndex =  "));

’ but no joy I also tried to add lcd.setCursor(0, 1); line in the code
This code at the moment is just displaying garbage all over LCD, When I inserted lcd.setCursor(0, 1); and so for each line to print but still the same it’s all over the LCD, Any hints where I’m going wrong

#include <MenuClass1.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

const int upButtonPin = 4;
const int downButtonPin = 5;
const int selectButtonPin = 6;


/*****************************
 ******************************
 * Define the new derived menu class
 * implement the functions to update 
 * the menu, make a selection, and
 * display the menu
 ******************************
 *****************************/

class SimplelcdMenu: 
public MenuClass {

public:
  int updateSelection();
  boolean selectionMade();
  void displayMenu();

};

int SimplelcdMenu::updateSelection() {
  // This is a simplified example
  // Edge detection and debouncing are exercises left to the user
  if(digitalRead(upButtonPin) == LOW){
    return 1;
  }
  if(digitalRead(downButtonPin) == LOW){
    return -1;
  }
  return 0;
}

boolean SimplelcdMenu::selectionMade() {
  // This is a simplified example
  // Edge detection and debouncing are exercises left to the user
  return !digitalRead(selectButtonPin);  
}

void SimplelcdMenu::displayMenu() {

  //  Blink WIthout Delay to keep prints from blasting out too fast. 
  static unsigned long preMil = millis();
  unsigned long curMil = millis();
  if(curMil - preMil >= 500) {  // print every half second
    preMil = curMil;

    char outBuf[17];

    for (int i = 0; i < currentMenu->getSize(); i++) {

      if (i == currentItemIndex) {
        lcd.print(F("---> "));
      }
      else {
        lcd.print(F("     "));
      }
      // use getText method to pull text out into a buffer you can print
      getText(outBuf, i);
      lcd.println(outBuf);
    }
    lcd.print(F("currentItemIndex =  "));
    lcd.println(currentItemIndex);
    for (int i = 0; i < 3; i++) {  // put some spaces before next print
      lcd.println(" ");
    }
  }
}



/*****************************
 ******************************
 * Setup the menu as an array of MenuItem
 * Create a MenuList and an instance of your 
 * menu class
 ******************************
 *****************************/

MenuItem PROGMEM firstList[3] = { 
  { 
    "Item_one", fun1    }
  , { 
    "Item_two", fun2    }
  , { 
    "Menu_2", gotoMenuTwo    }
};

MenuItem PROGMEM secondList[3] = {
  {
    "Back" , gotoMenuOne   }
  ,{
    "Item_three" , fun3   }
  ,{
    "Item_four" , fun4   }
};

MenuList list1(firstList, 3);
MenuList list2(secondList, 3);

SimplelcdMenu myMenu;



void setup(){
  lcd.begin(20, 4);                              // Initialize the LCD.
  lcd.clear();
  pinMode(upButtonPin, INPUT_PULLUP);
  pinMode(downButtonPin, INPUT_PULLUP);
  pinMode(selectButtonPin, INPUT_PULLUP);

  //lcd.begin(19200);
  myMenu.setCurrentMenu(&list1);

  lcd.println(F("Running Menu Test:"));
}

void loop(){
  myMenu.doMenu();

  //  Whatever other code you have.  
  //  Try to keep it non-blocking while the 
  //  menu is displayed  
  delay(250);  // because my buttons aren't debounced. 
}



/*****************************
 ******************************
 * Define the functions you want your menu to call
 * They can be blocking or non-blocking
 * They should take no arguments and return a boolean
 * true if the function is finished and doesn't want to run again
 * false if the function is not done and wants to be called again
 ******************************
 *****************************/


boolean fun1() {
  // blocking functions that simply return true will be run once and will complete
  //  before the code goes to the next loop
  lcd.println(F("FUN 1:  running right now and printing this long line"));
  delay(3000);
  return true;
}

boolean fun2() {
  lcd.println(F("FUN 2:  running right now and printing this long line"));
  delay(3000);
  return true;
}

boolean fun3() {
  lcd.println(F("FUN 3:  locked here until button push"));
  delay(100);
  if(digitalRead(selectButtonPin) == LOW){
    return true;  // signal finished if button pressed
  }
  else {
    return false;  // no button press, keep running
  }
}

boolean fun4() {
  // remember you have to have at least one case return true or you lock things up
  lcd.println(F("FUN 4:  Locked here forever"));
  delay(3000);
  return false;
}

boolean gotoMenuOne() {
  myMenu.setCurrentMenu(&list1);
  return true;
}

boolean gotoMenuTwo() {
  myMenu.setCurrentMenu(&list2);
  return true;
}

If you write a simple code that does nothing but display to the LCD does that work right?

Delta_G: If you write a simple code that does nothing but display to the LCD does that work right?

Yeah the display works fine, I loaded another project and this displays correctly in the correct line correct Colum and if I move line numbers so does text

I don't like println on an LCD. Sometimes it can cause issues. If I count right, you're printing 6 lines on a 4 line display. Can you try instead just putting the cursor where you want and printing what you want there?

I see now yes there is six menu selections,I thought it would continue to scroll the first 4 and so will have another read through the code, plus again print to serial port and see what's going on to get a better understanding. My mistake

Delta_G:

First off, thanks for posting some original work - they say timing is everything, and as it just so happens I’m looking for a nicer menu system for my data logger.

The hardware I’ve built has a rotary encoder (w/ pushbutton) and an OLED display that I’m treating like a large, low-power LCD display (makes a nice 8x21 display for text).

I’ve written a nested menu system that works fine but… well it’s 500 lines of horrible code that isn’t as easy to modify as I’d like. Works fine for version 1.00 but now I’m trying to replace it with something I can maintain and extend so I’ve been playing with your library to see how it works.

I don’t know if it will help, but I created a quick and dirty “library” using your posted files and have attached it to this message. I added a keywords.txt file along with proper library structure just it case it helps someone else.

To use, download the attachment and extract it into a folder called ‘MenuClass’. Move this folder to your Arduino libraries folder and enjoy!

I do have some issues though - specifically, I have to ask - do you really need to call ‘displayMenu’ every time thru the loop? Wouldn’t it be better to only call displayMenu when it’s needed?

I guess by that I mean the first time the menu is shown OR any time the user presses a button (to either navigate to another menu item OR to select the current item).

Otherwise I’d expect the display to be mostly static.

Does that make sense?

I’ve been playing with a custom version of the code that mostly works as I’d like (basically I added a variable called ‘lastItemIndex’ to ‘MenuClass’ and then in ‘doMenu’ only call ‘displayMenu’ if this value is different than ‘currentItemIndex’. Afterwards, of course, I set ‘lastItemIndex’ equal to ‘currentItemIndex’).

I also set ‘lastItemIndex’ to -1 in the two constructors to force the initial display. I’d post it here but It’s a bit specific to the hardware I’m using.

I know your example is a minimal implementation but I wonder how you handle redisplay after a menu item is selected?

Regards,

Brad
KF7FER

MenuClass.zip (3.75 KB)

I have doMenu called every pass of loop. The user shouldn't be calling displayMenu, just implementing it. If you wan to implement it so that if nothing changes it does nothing that would be fair.

The code I've been using it in displayMenu just populates a buffer to be displayed to an LCD. The overhead of writing into that buffer never concerned me. The codes I use this on aren't really that sensitive to speed or timing an it's not like I'm going to wear anything out overwriting that buffer with the same data. I know it's not necessarily optimized, but there have always been bigger fish to fry than working with that.

Please feel free to make any improvements you like. Look in the exhibition section of the forum and go to the github page for REBL_UI and fork it. I'd love to have the changes there. I'll pull them into my code if they work.

REBL_UI thread

Delta_G: I have doMenu called every pass of loop. The user shouldn't be calling displayMenu, just implementing it.

I wasn't talking about calling displayMenu directly; simply calling doMenu in a loop without a large delay (because my button is debounced) results in calling displayMenu many times per second.

I didn't mean to be critical; I was just trying to see how this library could work for me.

I thought perhaps you had a more elegant solution to the problem than I did.

Regards,

Brad.

Ok, just making sure what you meant there. No, I don't have anything better. In REBL, displayMenu just copies data into the buffer and there is a blink without delay style timing that only updates the actual display from that buffer every so often. Half a second IIRC.

And I don't take it as critical. Well I do but the good kind. That's why I posted it in the first place and why I love open source so much. I knew someone would see a way to improve upon what I built. I really do invite everyone to fork that project and do anything you like, good or bad. If I don't get a 4 x 20 display soon, someone should fix it so it scrolls the pointer on 4 line displays instead of just the text.

This code has been updated a little. The newest code can be found inside my REBL_UI along with an implementation for use on LCD with rotary encoder and button for input.

tried your code,, but im getting this error

SimpleSerialMenu:87: error: 'option1' was not declared in this scope

"Item_one", option1 }

^

SimpleSerialMenu:89: error: 'option2' was not declared in this scope

"Item_two", option2 }

^

SimpleSerialMenu:91: error: 'gotoMenuTwo' was not declared in this scope

"Menu_2", gotoMenuTwo }

^

SimpleSerialMenu:96: error: 'gotoMenuOne' was not declared in this scope

"Back" , gotoMenuOne }

^

SimpleSerialMenu:98: error: 'option3' was not declared in this scope

"Item_three" , option3 }

^

SimpleSerialMenu:100: error: 'option4' was not declared in this scope

"Item_four" , option4 }

^

exit status 1 'option1' was not declared in this scope

[quote author=Brad Burleson link=msg=2474991 date=1447295205] Delta_G:

First off, thanks for posting some original work - they say timing is everything, and as it just so happens I'm looking for a nicer menu system for my data logger.

The hardware I've built has a rotary encoder (w/ pushbutton) and an OLED display that I'm treating like a large, low-power LCD display (makes a nice 8x21 display for text).

I've written a nested menu system that works fine but... well it's 500 lines of horrible code that isn't as easy to modify as I'd like. Works fine for version 1.00 but now I'm trying to replace it with something I can maintain and extend so I've been playing with your library to see how it works.

I don't know if it will help, but I created a quick and dirty "library" using your posted files and have attached it to this message. I added a keywords.txt file along with proper library structure just it case it helps someone else.

To use, download the attachment and extract it into a folder called 'MenuClass'. Move this folder to your Arduino libraries folder and enjoy!

I do have some issues though - specifically, I have to ask - do you really need to call 'displayMenu' every time thru the loop? Wouldn't it be better to only call displayMenu when it's needed?

I guess by that I mean the first time the menu is shown OR any time the user presses a button (to either navigate to another menu item OR to select the current item).

Otherwise I'd expect the display to be mostly static.

Does that make sense?

I've been playing with a custom version of the code that mostly works as I'd like (basically I added a variable called 'lastItemIndex' to 'MenuClass' and then in 'doMenu' only call 'displayMenu' if this value is different than 'currentItemIndex'. Afterwards, of course, I set 'lastItemIndex' equal to 'currentItemIndex').

I also set 'lastItemIndex' to -1 in the two constructors to force the initial display. I'd post it here but It's a bit specific to the hardware I'm using.

I know your example is a minimal implementation but I wonder how you handle redisplay after a menu item is selected?

Regards,

Brad KF7FER

[/quote]

option1, option2??? That doesn't look like something I had in my code. Are you running the code in posts 1 and 2 of this thread? Can you post exactly the code you used?

Hi, thank you so much for this lib, i like it a lot.. [u]it has lowest sram usage of all tested so far[/u]

using it with no problem as is, but i would like to have the typedeffed structures of "MenuItems" in both progmem and sram to be able both save sram space and make menu names "interactive" by strcpy to "MenuItem" array directly.. this makes all of my menu dreams come true..

i tried to typedef "MenuItems" without "const" so its writable, but it takes lots of sram due to 16B + pointer per item line..

also tried to add a new type to MenuClass.h

typedef struct EditableMenuItem_t {
 char text[16];
 Item_Function func;
} EditableMenuItem;

but did end up duplicating functions due to different type..

do you have any hint how to reach this, or its not possible to do with this lib? :grin:

jasonmrc:
tried your code, but im getting this error

SimpleSerialMenu:87: error: ‘option1’ was not declared in this scope

“Item_one”, option1 }

^

SimpleSerialMenu:89: error: ‘option2’ was not declared in this scope

“Item_two”, option2 }

^

SimpleSerialMenu:91: error: ‘gotoMenuTwo’ was not declared in this scope

“Menu_2”, gotoMenuTwo }

^

SimpleSerialMenu:96: error: ‘gotoMenuOne’ was not declared in this scope

“Back” , gotoMenuOne }

^

SimpleSerialMenu:98: error: ‘option3’ was not declared in this scope

“Item_three” , option3 }

^

SimpleSerialMenu:100: error: ‘option4’ was not declared in this scope

“Item_four” , option4 }

^

exit status 1
‘option1’ was not declared in this scope

I got exactly the same error messages but with the original script from the topicstart.

At first I got:

SimpleSerialMenu:1:23: error: MenuClass.h: No such file or directory

 #include <MenuClass.h>

                       ^

compilation terminated.

exit status 1
MenuClass.h: No such file or directory

Then I changed

#include <MenuClass.h>

into

#include "MenuClass.h"

to let Arduino search for the local placed file.
After that I got the same errors as jasonmrc:

SimpleSerialMenu:87:17: error: 'fun1' was not declared in this scope

     "Item_one", fun1    }

                 ^

SimpleSerialMenu:89:17: error: 'fun2' was not declared in this scope

     "Item_two", fun2    }

                 ^

SimpleSerialMenu:91:15: error: 'gotoMenuTwo' was not declared in this scope

     "Menu_2", gotoMenuTwo    }

               ^

SimpleSerialMenu:96:14: error: 'gotoMenuOne' was not declared in this scope

     "Back" , gotoMenuOne   }

              ^

SimpleSerialMenu:98:20: error: 'fun3' was not declared in this scope

     "Item_three" , fun3   }

                    ^

SimpleSerialMenu:100:19: error: 'fun4' was not declared in this scope

     "Item_four" , fun4   }

                   ^

exit status 1
'fun1' was not declared in this scope

The only things changed are the button assignments since I use a Wemos D1 mini clone:

const int upButtonPin = D3;
const int downButtonPin = D7;
const int selectButtonPin = D6;

and changed the corresponding pinmodes to the ones needed:

pinMode(upButtonPin, INPUT_PULLUP);
pinMode(downButtonPin, INPUT);
pinMode(selectButtonPin, INPUT);

For the up-button I use the only available internal pull-up resistor on pin D3 of the Wemos D1 mini clone. For the down-button and select-button I use pins D7 and D6 with an external 4,7K pull-up resistor.

It is a bummer since I finally thought to have found a great start for a menu with my little Arduino programming knowledge. Does anyone have an idea how to solve this?

To the folks getting the error about the functions not being declared. This has something to do with how the Arduino build processes changed a few versions ago and for some reason the IDE isn’t adding the function declarations in the right place.

If you add this to the top of the file:

boolean fun1();
boolean fun2();
boolean fun3();
boolean fun4();
boolean gotoMenuOne();
boolean gotoMenuTwo();

before anything then it will compile. It just needs the forward declarations added by hand for some reason. Probably has to do with me having a class defined in that file.

So the new code looks like this, with the top being the only change.

boolean fun1();
boolean fun2();
boolean fun3();
boolean fun4();
boolean gotoMenuOne();
boolean gotoMenuTwo();

#include <MenuClass.h>

const int upButtonPin = 4;
const int downButtonPin = 5;
const int selectButtonPin = 6;


/*****************************
 ******************************
 * Define the new derived menu class
 * implement the functions to update 
 * the menu, make a selection, and
 * display the menu
 ******************************
 *****************************/

class SimpleSerialMenu: 
public MenuClass {

public:
  int updateSelection();
  boolean selectionMade();
  void displayMenu();

};

int SimpleSerialMenu::updateSelection() {
  // This is a simplified example
  // Edge detection and debouncing are exercises left to the user
  if(digitalRead(upButtonPin) == LOW){
    return 1;
  }
  if(digitalRead(downButtonPin) == LOW){
    return -1;
  }
  return 0;
}

boolean SimpleSerialMenu::selectionMade() {
  // This is a simplified example
  // Edge detection and debouncing are exercises left to the user
  return !digitalRead(selectButtonPin);  
}

void SimpleSerialMenu::displayMenu() {

  //  Blink WIthout Delay to keep prints from blasting out too fast. 
  static unsigned long preMil = millis();
  unsigned long curMil = millis();
  if(curMil - preMil >= 500) {  // print every half second
    preMil = curMil;

    char outBuf[17];

    for (int i = 0; i < currentMenu->getSize(); i++) {

      if (i == currentItemIndex) {
        Serial.print(F("---> "));
      }
      else {
        Serial.print(F("     "));
      }
      // use getText method to pull text out into a buffer you can print
      getText(outBuf, i);
      Serial.println(outBuf);
    }
    Serial.print(F("currentItemIndex =  "));
    Serial.println(currentItemIndex);
    for (int i = 0; i < 3; i++) {  // put some spaces before next print
      Serial.println();
    }
  }
}



/*****************************
 ******************************
 * Setup the menu as an array of MenuItem
 * Create a MenuList and an instance of your 
 * menu class
 ******************************
 *****************************/

MenuItem PROGMEM firstList[3] = { 
  { 
    "Item_one", fun1    }
  , { 
    "Item_two", fun2    }
  , { 
    "Menu_2", gotoMenuTwo    }
};

MenuItem PROGMEM secondList[3] = {
  {
    "Back" , gotoMenuOne   }
  ,{
    "Item_three" , fun3   }
  ,{
    "Item_four" , fun4   }
};

MenuList list1(firstList, 3);
MenuList list2(secondList, 3);

SimpleSerialMenu myMenu;



void setup(){
  pinMode(upButtonPin, INPUT_PULLUP);
  pinMode(downButtonPin, INPUT_PULLUP);
  pinMode(selectButtonPin, INPUT_PULLUP);

  Serial.begin(19200);
  myMenu.setCurrentMenu(&list1);

  Serial.println(F("Running Menu Test:"));
}

void loop(){
  myMenu.doMenu();

  //  Whatever other code you have.  
  //  Try to keep it non-blocking while the 
  //  menu is displayed  
  delay(250);  // because my buttons aren't debounced. 
}



/*****************************
 ******************************
 * Define the functions you want your menu to call
 * They can be blocking or non-blocking
 * They should take no arguments and return a boolean
 * true if the function is finished and doesn't want to run again
 * false if the function is not done and wants to be called again
 ******************************
 *****************************/


boolean fun1() {
  // blocking functions that simply return true will be run once and will complete
  //  before the code goes to the next loop
  Serial.println(F("FUN 1:  running right now and printing this long line"));
  delay(3000);
  return true;
}

boolean fun2() {
  Serial.println(F("FUN 2:  running right now and printing this long line"));
  delay(3000);
  return true;
}

boolean fun3() {
  Serial.println(F("FUN 3:  locked here until button push"));
  delay(100);
  if(digitalRead(selectButtonPin) == LOW){
    return true;  // signal finished if button pressed
  }
  else {
    return false;  // no button press, keep running
  }
}

boolean fun4() {
  // remember you have to have at least one case return true or you lock things up
  Serial.println(F("FUN 4:  Locked here forever"));
  delay(3000);
  return false;
}

boolean gotoMenuOne() {
  myMenu.setCurrentMenu(&list1);
  return true;
}

boolean gotoMenuTwo() {
  myMenu.setCurrentMenu(&list2);
  return true;
}