Thursday, June 21, 2018

ESP32 'Test Gadget' Menu System

I decided to go with a menu system very similar to what I had used in several of my other projects.  Now it uses the joystick vertical axis info to move through the menu items and a 'LongPress' on the button to make a selection.  It can be expanded to use the horizontal axis info and a 'ShortPress' if additional capabilities are desired.

Multiple menus can be created by defining different sets of definitions and variables for each menu, along with different versions of the two menu functions.   I use a naming convention of mnuName...   where Name  where NAME specifies which menu.
For the 'Test Gadget' there might be a mnuMain..., mnuVoltmeter...,
mnuSigGen... .

Each menu uses a block of global definitions and variables, with each member using the stated naming convention.  For my main menu they are.

#define  mnuMainNum    3       // number of menu items  0 referenced

#define  mnuMainX      20        // menu x and y position
#define  mnuMainY      20
#define  mnuMainTSize  3                      // text size
#define  mnuMainTCol   YELLOW       // text color
#define  mnuMainTBak   BLUE            // text background color
int    mnuMainSel   = 1 ;                        // selected menu item
// menu looks better if you pad items with spaces to make lengths equal

String   mnuMainItems[] = {" Meters   ", " Waveform ", " LCD      "," Sub Menu "};


For this test example I also have a sub menu that is called from the main menu.  Its global values are.

#define  mnuSubNum    2       // number of menu items  0 referenced

#define  mnuSubX      120     // menu x and y position
#define  mnuSubY     100
#define  mnuSubTSize  3                 // text size
#define  mnuSubTCol   BLUE        // text color
#define  mnuSubTBak   CYAN       // text background color
int    mnuSubSel   = 1 ;                    // selected menu item
// menu looks better if you pad items with spaces to make lengths equal

String   mnuSubItems[] = {" sub1 ", " sub2 ", " sub3 "};

I find it really easy to just do a copy and paste, and then using the Arduino IDE find and replace function to quickly make changes to the names.

Most of the work in the menu function is a function called
showNAMEMenuItems(), as with the definitions and variables you will have to change the name for each menu.  For the Main menu it looks like this. .

void showMainMenuItems() {

  tft.setTextSize(mnuMainTSize);
  for (int i = 0; i <= mnuMainNum ; i++) {
    if (i == mnuMainSel )
      tft.setTextColor( mnuMainTBak ,  mnuMainTCol );  // inverted colors
    else
      tft.setTextColor(mnuMainTCol, mnuMainTBak );     // normal colors
     // calculate proper x y position and set cursor
    tft.setCursor( mnuMainX  , mnuMainX + (i * 8 * mnuMainTSize));
    tft.println(mnuMainItems[i]);
  }

}
 It uses the global values and variables to show the menu items at the correct x,y position on the screen.  Each menu item is tested to see if it is the selected one, and the text/background colors are inverted from the non selected item.

The other function in the menu system is a function called
ShowMenuNAME() . It uses a local variable inMenu to control a while loop that reads the joystick and checks the vertical axis value to select the desired menu item.   It calls showNAMEMenuItems()
to update the menu after any change.  It also tests the joystick switch for a 'LongPress' which causes the while loop to end.

void ShowMenuMain() {
  boolean inMenu = true;
  showMainMenuItems();
  delay(200);
  while (inMenu) {
    ReadJoyStick();
    delay(20);
    if (jstick) {
      if (jstickV ) {
        mnuMainSel += jstickV ;
        if ( mnuMainSel < 0)  mnuMainSel = 0;
        if (mnuMainSel >= mnuMainNum )  mnuMainSel = mnuMainNum;
        showMainMenuItems();
      }
      if (jstickSW == LongPress)
        inMenu = false;         // long press gets you out of the menu loop
    }
    jstick = false;
  }

After the while(inMenu) loop ends you can use either a series of if statements , or a switch- case function to select what is done for each menu item.  For this test example I used a series of if statements , mainly because I was just copying existing code for testing.  If I had already written specific functions for each menu item, I would probably have gone with a switch-case instead.

if (mnuMainSel == 0) {
    tft.fillScreen(BLACK);
    drawMeter(MTR1_X, MTR1_Y, MTR1_W , MTR1_H, WHITE, WHITE, BLACK,
     "  1 2 3 4 5 6 7 8 9 10");
    drawMeter(MTR2_X, MTR2_Y, MTR2_W , MTR2_H, WHITE, BLUE, YELLOW,
    "  1 2 3 4 5 6 7 8 9 10");
    plotNeedle(MTR1_X, MTR1_Y, MTR1_W , MTR1_H, 150, 300,  BLACK);
    plotNeedle(MTR2_X, MTR2_Y, MTR2_W , MTR2_H, 250, 400, BLUE);
  }
  if (mnuMainSel == 1) {
    tft.fillScreen(BLACK);
    DrawWaveformScreen(BLUE, CYAN, 10);
    DrawYScale(0 , 10, 10);
  }
  if (mnuMainSel == 2) {
    tft.fillScreen(BLACK);
    lcd_begin(10, 60, 16, 4, 3,  BLUE,  YELLOW) ; // text and background color
    tft.setCursor(0, 0);
    tft.setTextColor(lcdTxtColor);
    tft.setTextSize(lcdTxtSize);
    for (int i = 0; i < lcdRow; i++) {
      lcd_setCursor(0, i);
      lcd_print("12345");
      lcd_setCursor(5, i);
      lcd_printNum(6, 0);
      lcd_print("7890123456");
    }
  }
  if (mnuMainSel == 3) {
    ShowMenuSub();
  }

}


This test code this shows a simple menu, which lets you select one of the test graphic display types from the last blog entry.  The other option is a sub menu that only exits when you do a 'LongPress'.
In each case the display stays around for 5 seconds then clears the screen and shows the main menu again.The two functions for the sub menu are basically the same as the main menu code with the names changed.

void ShowMenuSub() {
  boolean inMenu = true;
  showSubMenuItems();
  delay(200);
  while (inMenu) {
    ReadJoyStick();
    delay(20);
    if (jstick) {
      if (jstickV ) {
        mnuSubSel += jstickV ;
        if ( mnuSubSel < 0)  mnuSubSel = 0;
        if (mnuSubSel >= mnuSubNum )  mnuSubSel = mnuSubNum;
        showSubMenuItems();
      }
      if (jstickSW == LongPress)
        inMenu = false;         // long press gets you out of the menu loop
    }
    jstick = false;
  }
  // out of menu item selection now do selected item
  // you can do as multiple if statements
  // or a switch  -  case
  //  FOR THIS DEMO JUST EXIT
}

void showSubMenuItems() {
  tft.setTextSize(mnuSubTSize);
  for (int i = 0; i <= mnuSubNum ; i++) {
    if (i == mnuSubSel )
      tft.setTextColor( mnuSubTBak ,  mnuSubTCol );  // inverted colors
    else
      tft.setTextColor(mnuSubTCol, mnuSubTBak );     // normal colors
    tft.setCursor( mnuSubX  , mnuSubX + (i * 8 * mnuSubTSize));
    tft.println(mnuSubItems[i]);
  }
}


Here is a picture of what the menu looks 
like after the sub menu was selected.  
The same code can be easily modified to use a rotary encoder with switch instead of the joystick.  I have found this to be a relatively simple menu system to implement.  It may use more program space than some of the complete menu management sytems, but most of my applications have only need one or two menu selections and in that case may be smaller.


No comments:

Post a Comment