[UPDATE]
Anyone wanting a menu class for a LCD should look at This Post.
This class supports infinitely nested menus, scrolling, and callbacks for each menu item.
I've made a hierarchal menu class for a 20x4 LiquidCrystal, and figured it might be useful to someone else. I'd also like to get some feedback as to whether I'm doing anything stupid or anything that should be fixed. It's not super flexible but it gets the job done.
#include <LiquidCrystal.h>
LiquidCrystal lcd(2,-1,3,4,5,6,7);
typedef boolean (*MYCALL)(void);
class menu
{
private:
int numchildren; //Current number of children
menu * children[8];//Pointers to children
int inchild; //This variable is not checked for < numchildren, so be careful when setting it
MYCALL callback; //Function to call when this menu is clicked.
//Return true to go into the menu, false if the menu is just an item to perform a function
public:
char *name;
menu(char *n,MYCALL c)
{
name=n;
numchildren=0;
inchild=-1;
callback=c;
}
void addchild(menu &child)
{
if (numchildren<7)
{
children[numchildren++]=&child;
}
else
{
serror("Too many children");
}
}
void display()
{
Serial.print(name);
Serial.println(" Display");
if (inchild==-1)
{
lcd.clear();
for (int i=0;i<numchildren;i++)
{
lcd.setCursor(((i%2)*10)+1,i/2);
lcd.print(children[i]->name);
//lcd.print("Test");
}
if (name!="Root")
{
lcd.setCursor(11,3);
lcd.print("Back");
}
}
else
{
children[inchild]->display();
}
}
void gochild(int thechild)
{
Serial.print(name);
Serial.println(" Gochild");
if (inchild==-1)
{
if (thechild<numchildren)
{
if (children[thechild]->callback())
{
inchild=thechild;
display();
}
}
else if (thechild==7&&name!="Root")
{
goupx();
}
}
else
{
children[inchild]->gochild(thechild);
}
}
boolean goup()
{
Serial.print(name);
Serial.print(" Goup ");
if (inchild==-1)
{
Serial.println("1");
return 1; //This one isn't in a menu and is ready to be left
}
else if (children[inchild]->goup())//The child was ready to be left
{
Serial.println("0");
inchild=-1;
return 0;//Keep the next one from going up
}
else //Shouldn't happen
{
serror("Error Going Up");
}
}
};
void serror(char *error)
{
lcd.clear();
lcd.print("ERROR");
lcd.setCursor(0,1);
lcd.print(error);
}
boolean none()
{
Serial.println("none");
return 0;
}
boolean some()
{
Serial.println("some");
return 1;
}
menu Root("Root",none);
void goupx()
{
Root.goup();
Root.display();
}
menu Item1("Something",some);
menu Item11("Stuff",none);
menu Item12("More",some);
menu Item121("Deeper",none);
menu Item2("Other",none);
menu Item3("Etc",some);
menu Item31("So On",none);
void menuinit()
{
Root.addchild(Item1);
Root.addchild(Item2);
Root.addchild(Item3);
Item1.addchild(Item11);
Item1.addchild(Item12);
Item12.addchild(Item121);
Item3.addchild(Item31);
}
//UP,DOWN,LEFT,RIGHT,ENTER
int but[5]={8,9,10,11,12};
//Previous States of buttons
boolean pbut[5]={0,0,0,0,0};
int curloc;
boolean dread(int pin)
{
return digitalRead(pin);
}
void buttoncheck()
{
for (int i=0;i<5;i++)
{
if (dread(but[i]))
{
if (pbut[i]==0)
{
button(i);
pbut[i]=1;
}
}
else
{
pbut[i]=0;
}
}
}
void button(int which)
{
switch (which)
{
case 0://UP
curloc-=2;
break;
case 1://DOWN
curloc+=2;
break;
case 2://LEFT
curloc--;
break;
case 3://RIGHT
curloc++;
break;
case 4://ENTER
Root.gochild(curloc);
break;
}
if (curloc<0){curloc+=8;}
curloc%=8;
}
void setup()
{
Serial.begin(9600);
Serial.println(sizeof(menu));
lcd.clear();
menuinit();
Root.display();
lcd.command(0x0F);
}
void loop()
{
lcd.setCursor((curloc%2)*10,curloc/2);
buttoncheck();
}
The dread() function is because I plan to put the buttons on a shift register eventually, so then I can just edit that function. The some and none functions are just placeholders, the point being that you could give an item a function that did something useful before it returned.
Eventually I plan to read config info in from an SD card and generate the menu items with malloc.
Clearly there's still debug stuff in there, I just wanted to get some feedback.