jonblack / arduino-menusystem

Arduino library for implementing a menu system
MIT License
194 stars 85 forks source link

Trying to iterate through menus #41

Closed rfcohen closed 7 years ago

rfcohen commented 8 years ago

I'm trying to use this library as the model for visual menu system on a 320x480 LCD and I'm having trouble. I think each menu or menu item should have a unique identifier in a given menu.

menu.get_current_component_num() seems to suggest that too.

I have a menu structure set up like this:

Menu mu0("Base Config");
MenuItem mm_00("Time & Date", &on_item00_selected);
MenuItem mm_01("Set PIN", &on_item02_selected);
MenuItem mm_02("Backlight/Contrast", &on_item03_selected);

Menu mu1("Sensor Config");
MenuItem mm_10("Module1/Module2", &on_item10_selected);
MenuItem mm_11("Probe Voltage", &on_item12_selected);
MenuItem mm_12("Units", &on_item15_selected);

Menu mu2("Calib");
MenuItem mm_20("New Calibration", &on_item20_selected);
MenuItem mm_21("Load Calibraion", &on_item21_selected);

And then I build up the menu like this:

  mu0.add_item(&mm_00);
  mu0.add_item(&mm_01);
  mu0.add_item(&mm_02);

  ms.get_root_menu().add_menu(&mu1);
  mu1.add_item(&mm_10);
  mu1.add_item(&mm_11);
  mu1.add_item(&mm_12);

  ms.get_root_menu().add_menu(&mu2);
  mu2.add_item(&mm_20);
  mu2.add_item(&mm_21);

I have a stupid simple renderer (btw, it would be nice if the documentation discussed the need for a renderer):

class MyRenderer : public MenuComponentRenderer
{
public:
    virtual void render(Menu const& menu) const
    {
        menu.get_current_component()->render(*this);
    }

    virtual void render_menu_item(MenuItem const& menu_item) const
    {
        Serial.println(menu_item.get_name());
    }

    virtual void render_back_menu_item(BackMenuItem const& menu_item) const
    {
        Serial.println(menu_item.get_name());
    }

    virtual void render_numeric_menu_item(NumericMenuItem const& menu_item) const
    {
        Serial.println(menu_item.get_name());
    }

    virtual void render_menu(Menu const& menu) const
    {
        static byte i = 0;
        Adafruit_GFX_Button button;

//        Serial.println(menu.get_name());

//        MenuComponent const* comp = menu.get_current_component();

        uint8_t menu_num = menu.get_current_component_num(); // prints all zeros
        Serial.println(menu_num);

        switch (i) {
          case 0:
          case 1:
            button.initButton(&tft, 120 + 240*i, 100, 200, 60, HX8357_GREEN, HX8357_BLACK, HX8357_WHITE, (char*)menu.get_name(), 2);
            button.drawButton();
            break;
          case 2:
          case 3:
            button.initButton(&tft, 120 + 240 *(i-2), 200, 200, 60, HX8357_GREEN, HX8357_BLACK, HX8357_WHITE, (char*)menu.get_name(), 2);
            button.drawButton();
            break;
          default:
            break;
        }

        if (i++ == 3) {
          i=0;
        }
    }
};

(the switch is a hack and all I could do to get what I wanted to draw)

My questions: 1 What portion of the renderer gets called when? 2 Given a menu passed into the renderer, what's the right way to find its spot in the hierarchy for its parent? I'm hoping there's a simple way to get the menu's 'number' and use that as an index for drawing. So in my case, when the menu being drawn is mu1 is there a way to know that this is menu 1? 3 What's a component? A menu, a menu item, both, neither?

(I think that's all I have for now. Thanks)

jonblack commented 8 years ago

You're right that more documentation is needed for the renderer and I have an issue for it, just not enough time to work on it at the moment.

1 What portion of the renderer gets called when?

When you call MenuSystem::display() it calls render on the Renderer passing a pointer the current Menu.

The Renderer::render function is the entry point to rendering. In here you need to decide which MenuComponent's you're showing and call render on them. So if you just want to show a single item, you'd get that item and call render; if you want to show everything, you'd want to loop over the entire menu and call render on each item.

When you call render on a MenuComponent is calls back to the appropriate render method in the renderer for the given component. For example, calling render on a NumericMenuItem will cause the NumericMenuItem to call render_numeric_menu_item. This function should have code to display this item.

2 Given a menu passed into the renderer, what's the right way to find its spot in the hierarchy for its parent? I'm hoping there's a simple way to get the menu's 'number' and use that as an index for drawing. So in my case, when the menu being drawn is mu1 is there a way to know that this is menu 1?

MenuComponent don't know where they are in the heirarchy, and they shouldn't. You have two options for achieving numbering:

  1. Hard code it into the menu name;
  2. Add a counter variable to the renderer and increment it each time you render an item; reset it once you're done.

3 What's a component? A menu, a menu item, both, neither?

If you look in MenuSystem.h you can see the classes for the library. The menu system is implemented using the composite design pattern. A MenuComponent is the component part of this pattern, which acts as a base class from which the leaf MenuItem and composite Menu classes derive. This allows you to build up a nested hierarchy.


Great questions by the way. This will help me when I get around to writing some documentation.

rfcohen commented 8 years ago

This is great and thank you. There's enough here for me to make progress and get myself in trouble. :)

One more question - what is a NumericMenuItem?

RobiGoBH commented 7 years ago

I hope JonBlack not will be upset if I help you in that. So, a NumericMenuItem and a CustomNumericMenuItem are special menu items. If you like to use a menu item to execute something when you selecting it, just use a MenuItem and use the method linked to it to do the logic what you want to execute. The NumericMenuItem is very handy when you like to use that menu item to store a numeric value, for example, the display brightness, or a language selector. The value will be rendered next to the menu item text like "Brightness=90" and if you press the select on that item it will be switched into edit mode and you will see something like "Brightness<90>" then pressing the up/down you can change the value what will be saved when you press the select again. You can use a custom renderer to show something like "Brightness=90%" or "Language=English" for more details check this example:

arduino-menusystem/examples/serial_nav/serial_nav.ino

Btw I'm happy to help if you will get in trouble. (I'm very close to finishing a woodchip boiler driver using a 320x480 TFT, with a multi-language menu system and settings saved back to SD card, (I will upload a youtube video very soon) so I struggled a lot to figure out how to make it nice for user and elegant from programming side.)

jonblack commented 7 years ago

Can this issue be closed now?

jonblack commented 7 years ago

I'm assuming this is now resolved. Re-open if it's not.