New Arduino library: MenuSystem

I was just starting to write a project that needs a menu when i saw your post... :slight_smile: , i'll give it a try.

Thanks for sharing.

Good to hear :slight_smile: If you have any questions, let me know. Good luck!

Hi

How do i display the menu? Is it intended to show the menu tree somewhere?

Oliver

Think of MenuSystem as a container for your menu system, holding the structure, current items, and callbacks. So, it won't display anything for you. What it does provide are three functions:

  • ms.get_current_menu_name(); // get the current menu name
  • ms.get_num_menu_items(); // get the number of menu items in the current menu
  • ms.get_cur_menu_item(); // get the current menu item number in the current menu

The first one is probably all you need. This returns the current menu name. Every time you call back(), next(), prev(), select() this will change. The other two functions return the current menu item number and total number of items in the current menu level. These are useful if you want to implement some kind of pagination.

Does that answer your question?

olikraus:
Hi

How do i display the menu? Is it intended to show the menu tree somewhere?

Oliver

So this means, I need to write a procedure which renders the menu on an output device. This procedure also has to take care to highlight the "current" menu item, correct?

Thanks for clarification,
Oliver

olikraus:
So this means, I need to write a procedure which renders the menu on an output device. This procedure also has to take care to highlight the "current" menu item, correct?

Thanks for clarification,
Oliver

Yes, you need to render the menu to a device. Bear in mind that this library manages which item is current for you, and that is the only item you should display. This means that you can only show one option on your output device at a time. If you want to display all menu items and highlight the current one, this library isn't for you. I've written some pseudo-code below to explain this, I hope it makes sense:

def process_input():
  if next_button_pressed:
    menusystem.next()
  else if prev_button_pressed:
    menusystem.prev()
  else if select_button_pressed:
    menusystem.select()
  else if back_button_pressed:
    menusystem.back()

def update_menu():
  current_item_text = menusystem.get_current_menu_name()
  output_device.write(current_item_text)

def loop():
  process_input()
  update_menu()

ok, now I understand.

Thanks
Oliver

To manage the menu tree i added a function to Menu class:

MenuComponent* Menu::get_item( int num )
{
    return _menu_components[num];
}

And print items this way for example:

	MenuComponent* selected = mm.get_selected();

	for( int i=0; i<mm.get_num_menu_items(); i++ )
	{
		MenuComponent* item = mm.get_item( i );
		gotoXY( 0, i );
		if( item == selected ) LCDString( ">");
		else                   LCDString( " ");
		LCDString( item->get_name() );
	}

By now i just have tested some basic things of the library and it is working very well.

When I developed my menu class, I faced two difficulties: displaying the menu and defining the options.

Here's there result:

A menu is declared this way:

// menu declaration and size
item myMenuItems[] = { 
  {     0x0000, "Menu 0"        }  ,    
  {     0x1000, "Item 1"        }  ,
  {     0x1100, "Item 11"       }  ,
  {     0x1200, "Item 12"       }  ,
  {     0x2000, "Item 2"        }  ,
  {     0x2100, "Item 21"       }  ,
  {     0x2110, "Item 211"      }  ,
  {     0x2120, "Item 212"      }  ,
  {     0x2121, "Item 2121"     }  ,
  {     0x2122, "Item 2122"     }  ,
  {     0x2200, "Item 22"       }  ,
  {     0x2300, "Item 23"       }  ,
  {     0x3000, "Item 3"        } 
};

Learn more about the menu and the Serial_LCD library suite :slight_smile:

Not sure if this is possible without doing changes, but i needed another function in MesuSystem class:

Menu* MenuSystem::get_current_menu()
{
    return _p_curr_menu;
}

And i print the current menu at it's items this way:

void updateMenu()
{
	LCDClear();

	Menu* currentMenu = menu_system.get_current_menu();

	MenuComponent* selected = currentMenu->get_selected();

	LCDString( currentMenu->get_name() );		// Displays current menu

	for( int i=0; i<currentMenu->get_num_menu_items(); i++ )
	{
		MenuComponent* item = currentMenu->get_item( i );

		gotoXY(  5, i+1 );

		if( item == selected ) LCDString( ">");	// Displays an arrow in current item
		else                   LCDString( " ");

		LCDString( item->get_name() );		// Displays items in current menu
	}
}

I see more ineresting have getter: MenuSystem::get_current_menu() intead of MenuSystem::get_current_menu_name(), with the first i can access to anything i need in the current menu, not only name.

Not entirely sure how this is relevant to the MenuSystem I've created. Looks to me like a shameless plug!

avenue33:
When I developed my menu class, I faced two difficulties: displaying the menu and defining the options.

A menu is declared this way:

// menu declaration and size

item myMenuItems[] = {
  {     0x0000, "Menu 0"        }  ,   
  {     0x1000, "Item 1"        }  ,
  {     0x1100, "Item 11"       }  ,
  {     0x1200, "Item 12"       }  ,
  {     0x2000, "Item 2"        }  ,
  {     0x2100, "Item 21"       }  ,
  {     0x2110, "Item 211"      }  ,
  {     0x2120, "Item 212"      }  ,
  {     0x2121, "Item 2121"     }  ,
  {     0x2122, "Item 2122"     }  ,
  {     0x2200, "Item 22"       }  ,
  {     0x2300, "Item 23"       }  ,
  {     0x3000, "Item 3"        }
};




Learn more about the [menu](http://embeddedcomputing.weebly.com/menu.html) and the [Serial_LCD](http://embeddedcomputing.weebly.com) library suite :)

Looks interesting. I'll test it and see if it fits well with the original design. It's nice that you can display multiple items and show the highlighted one, what I don't like is having to iterate over the menu components outside of the library - those should stay internal.

Perhaps a function in the menu system like so would be better:

char** MenuSystem::get_current_menu_options();

This will return an array of the menu options for the current menu. What do you think?

arcachofo:
Not sure if this is possible without doing changes, but i needed another function in MesuSystem class:

....

I see more ineresting have getter: MenuSystem::get_current_menu() intead of MenuSystem::get_current_menu_name(), with the first i can access to anything i need in the current menu, not only name.

Perhaps a function in the menu system like so would be better:

char** MenuSystem::get_current_menu_options();

This will return an array of the menu options for the current menu. What do you think?

Ok, this is a safer and easier (for the user) way to do it.

The only little disadvantage i see if i understand correctly is that MenuSystem has to iterate through items to create the array, and then the user has to iterate again through array to print names, what is little slower, but... all have a price.

Another cuestion: Why do you pass a pointer to MenuItem in the callback funtion?

Thinking about this library.. it could be used for anything that needs a tree structure, noy only a menu system.

jonblack:
Not entirely sure how this is relevant to the MenuSystem I've created. Looks to me like a shameless plug!

avenue33:
When I developed my menu class, I faced two difficulties: displaying the menu and defining the options.

Sorry for my contribution on menu indexing being considered as a shameless plug.

arcachofo:
Ok, this is a safer and easier (for the user) way to do it.

The only little disadvantage i see if i understand correctly is that MenuSystem has to iterate through items to create the array, and then the user has to iterate again through array to print names, what is little slower, but... all have a price.

Another cuestion: Why do you pass a pointer to MenuItem in the callback funtion?

Thinking about this library.. it could be used for anything that needs a tree structure, noy only a menu system.

I pass a pointer to the menu item to the callback so it knows what invoked it.

You're right about the double iteration. Still, optimising early is generally considered a bad idea. That said, I'm not totally against you're approach. I will try them both out :slight_smile:

Finally, you're right again, this could be used for any tree structure. That is the intent of the composite design pattern. I wouldn't use this library for anything other than a menu system though.

arcachofo, I had a look at the two options: it was fun. :slight_smile:

I was leaning towards my option, because I really wanted to prevent the user from doing something stupid like:

Menu* current_menu = ms.get_current_menu();
current_menu->select();

My idea was too hard to implement in a clean and tidy way (because arduino doesn't support new and delete). So I went back to your idea and the answer came to me:

Menu const* get_current_menu() const;

Yup! The function returns a pointer to a const. I made a lot of functions do the same, so if users try silly things, it won't work without them being extra silly and const_casting.

So, you'll be pleased to know that I've applied a slightly modified version of your idea and uploaded it to github. I've also included some examples. Thanks for the idea! Enjoy!

Menu const* get_current_menu() const;

Yup! The function returns a pointer to a const. I made a lot of functions do the same, so if users try silly things, it won't work without them being extra silly and const_casting.

Yes... looks the proper way, and if someones knows to do a cons_cast is suposed to know what is doing.

So, you'll be pleased to know that I've applied a slightly modified version of your idea and uploaded it to github. I've also included some examples. Thanks for the idea! Enjoy!

Nice!! i will have a look, thanks.

Regards.

Hello Jon

I'm bumping this old thread :slight_smile:

First, thanks for your menu class, this is exactly what I needed for my project.

I'm actually building an antenna tracker to track my drones using telemetry data.

Code is here: Google Code Archive - Long-term storage for Google Code Project Hosting.

On the ground device, there's an LCD ( i2c lcd03) proposing at startup a menu.

Here is the menu:

Menu Root:

  • START
  • SET_HOME
  • CONFIGURATION:
  • SERVOS:
  • PAN:
  • MIN_PWM
  • MIN_ANGLE
  • MAX_PWM
  • MAX_ANGLE
  • TILT:
  • MIN_PWM
  • MIN_ANGLE
  • MAX_PWM
  • MAX_ANGLE
  • TEST
  • BAUDRATE

Here is my init menu function:

void init_menu() {
	rootMenu.add_item(&m1i1Item, &screen_tracking); //start track
	rootMenu.add_item(&m1i2Item, &screen_sethome); //set home position
	rootMenu.add_menu(&m1m3Menu); //configure
		m1m3Menu.add_menu(&m1m3m1Menu); //config servos
			m1m3m1Menu.add_menu(&m1m3m1m1Menu); //config pan
				m1m3m1m1Menu.add_item(&m1m3m1m1l1Item, &configure_pan_minpwm); // pan min pwm
				m1m3m1m1Menu.add_item(&m1m3m1m1l2Item, &configure_pan_minangle); // pan min angle
				m1m3m1m1Menu.add_item(&m1m3m1m1l3Item, &configure_pan_maxpwm); // pan max pwm
				m1m3m1m1Menu.add_item(&m1m3m1m1l4Item, &configure_pan_maxangle); // pan max angle
			m1m3m1Menu.add_menu(&m1m3m1m2Menu); //config tilt
				m1m3m1m2Menu.add_item(&m1m3m1m2l1Item, &configure_tilt_minpwm); // tilt min pwm
				m1m3m1m2Menu.add_item(&m1m3m1m2l2Item, &configure_tilt_minangle); // tilt min angle
				m1m3m1m2Menu.add_item(&m1m3m1m2l3Item, &configure_tilt_maxpwm); // tilt max pwm
				m1m3m1m2Menu.add_item(&m1m3m1m2l4Item, &configure_tilt_maxangle); // tilt max angle
                        m1m3m1Menu.add_item(&m1m3m1i3Item, &configure_test_servo);
		m1m3Menu.add_item(&m1m3l2Item, &configure_baudrate); //config baudrate
	displaymenu.set_root_menu(&rootMenu);
}

Then in the mainloop, when current_activity == "MENU" , it calls at 10hz:

void display_menu() {
        Menu const* displaymenu_current = displaymenu.get_current_menu();
		//Serial.print("current menu = ");
		//Serial.println(displaymenu_current->get_cur_menu_component_num());
	MenuComponent const* displaymenu_sel = displaymenu_current->get_selected();
          
        for (int n = 1; n < 5; ++n) {
          char string_buffer[21];
	//
            if ( (displaymenu_current->get_num_menu_components()) >= n ) {
                
      		  
      		
      		  MenuComponent const* displaymenu_comp = displaymenu_current->get_menu_component(n-1);
      		  String getname = displaymenu_comp->get_name();
			  for ( int l = getname.length()-1 ; l<20 ; l++ ) {
				getname = getname + " ";
				}			  

      		  if (displaymenu_sel == displaymenu_comp) {
 
      				getname.setCharAt(19,'<');
      		  } else {
      		  		getname.setCharAt(19, ' ');
      		  }
      	
      		  getname.toCharArray(string_buffer,21);
                
            }
            else {
               empty_line.toCharArray(string_buffer,21);
            }
		store_lcdline(n, string_buffer);
		
	};
		
}

Then LCD screen is refreshed at 10hz on another timed loop in the main loop.

This works almost well.
When I stay inside the menu, I can go next, prev, back and select...
I use click button to select, & long_press button to go back to the parent.

The problem is when I select an item in a submenu. Imagine I've selected CONFIGURATION/TILT/MIN_PWM. My current_activity change from #MENU" to "TILT_MINPWM". ( so the display_menu function is not called anymore. )
It then call another function to display the configuration screen on the LCD & handle configuring parameters.
When I quit this screen, I change the current_status to "MENU", so it displays the menu again.
The problem is it doesn't go back to the previous menu state, it just start from the Root Menu, with the CONFIGURATION already selected.

I'd like to have it going back in menu_root/CONFIGURATION/SERVOS/TILT menu and not Root.

There's probably a mistake I've made somewhere, but I can't find it for now.

If you have a genius idea, I take it :slight_smile:

Cheers.

Forget it I've found it.

It's un the library, cpp file:

 // A menu item was selected, so reset the menu ready for when
// it's used again.
 _p_curr_menu = _p_root_menu;

So I've added mreset parameter at select() function:

void MenuSystem::select(boolean mreset)
{
    MenuComponent* pComponent = _p_curr_menu->activate();

    if (pComponent != NULL)
    {
        _p_curr_menu = (Menu*) pComponent;
    }
    else
    {
        
		if (mreset) {
		// A menu item was selected, so reset the menu ready for when
        // it's used again.
        _p_curr_menu = _p_root_menu;
		}
    }
}

Have anyone a complete code with this librarie so I can better understand and use to make my menu?

Thanks!