Skip to content
Alexander Spiridonov edited this page Dec 12, 2023 · 55 revisions

The following is a small reference on how to do common tasks when creating a menu:

Create menu object (AltSerialGraphicLCD version)

With default settings, where glcd is an object of class GLCD that uses SoftwareSerial to communicate with LCD equipped with SparkFun Graphic LCD Serial Backpack:

// Constants for the pins SparkFun Graphic LCD Serial Backpack is connected to and SoftwareSerial object
const byte rxPin = 8;
const byte txPin = 9;
SoftwareSerial serialLCD(rxPin, txPin);

// Instance of GLCD class
GLCD glcd(serialLCD);

// Menu object 
GEM menu(glcd);
// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary):
// GEM menu(glcd, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ 5, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86);

Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!

Create menu object (U8g2 version)

First, choose proper constructor of U8g2 library, based on LCD controller being used and desired configuration (U8G2_KS0108_128X64_1 in the example). Then proceed as follows:

// Create an instance of the U8g2 library.
// Use constructor that matches your setup (see https://github.com/olikraus/u8g2/wiki/u8g2setupcpp for details).
// This instance is used to call all the subsequent U8g2 functions (internally from GEM library,
// or manually in your sketch if it is required).
// Please update the pin numbers according to your setup. Use U8X8_PIN_NONE if the reset pin is not connected
U8G2_KS0108_128X64_1 u8g2(U8G2_R0, 8, 9, 10, 11, 12, 13, 18, 19, /*enable=*/ A0, /*dc=*/ A1, /*cs0=*/ A3, /*cs1=*/ A2, /*cs2=*/ U8X8_PIN_NONE, /* reset=*/ U8X8_PIN_NONE);   // Set R/W to low!

// Menu object
GEM_u8g2 menu(u8g2);
// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary):
// GEM_u8g2 menu(u8g2, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86);

Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!

Create menu object (Adafruit GFX version)

Adafruit GFX library supports several different display controllers (through separately installed and included libraries). Choose a matching library for the correct initialization of the display. See available libraries and supported controllers in the documentation for Adafruit GFX library.

In case of ST7735-based display, we need to create an instance of the Adafruit_ST7735 class named tft:

// Hardware-specific library for ST7735.
// Include library that matches your setup (see https://learn.adafruit.com/adafruit-gfx-graphics-library for details)
#include <Adafruit_ST7735.h>

// Macro constants (aliases) for the pins TFT display is connected to. Please update the pin numbers according to your setup
#define TFT_CS    A2
#define TFT_RST   -1 // Set to -1 and connect to Arduino RESET pin
#define TFT_DC    A3

// Create an instance of the Adafruit GFX library.
// Use constructor that matches your setup (see https://learn.adafruit.com/adafruit-gfx-graphics-library for details).
// This instance is used to call all the subsequent Adafruit GFX functions (internally from GEM library,
// or manually in your sketch if it is required)
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

// Menu object
GEM_adafruit_gfx menu(tft, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO);
// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary):
// GEM_adafruit_gfx menu(tft, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86);

Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!

Init and draw menu to the screen (AltSerialGraphicLCD version)

Generally in setup() function of the sketch:

// Serial communications setup
serialLCD.begin(115200);

// LCD reset
delay(500);
glcd.reset();
delay(1000);
// Uncomment the following lines in dire situations
// (e.g. when screen becomes unresponsive after shutdown)
glcd.reset();
delay(1000);

// Menu init
menu.init();

// Menu setup: add menu items to menu pages to menu here (see below for examples)
menuPageMain.addMenuItem(menuItemByte);
menu.setMenuPageCurrent(menuPageMain);

// Menu draw
menu.drawMenu();

Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!

Init and draw menu to the screen (U8g2 version)

Generally in setup() function of the sketch:

// U8g2 library init.
u8g2.begin();

// Menu init
menu.init();

// Menu setup: add menu items to menu pages to menu here (see below for examples)
menuPageMain.addMenuItem(menuItemByte);
menu.setMenuPageCurrent(menuPageMain);

// Menu draw
menu.drawMenu();

Optionally, to utilize built-in key press event detection features of U8g2 library, supply u8g2.begin() method with pin numbers the push-buttons are attached to (read more for details):

// U8g2 library init. Pass pin numbers the buttons are connected to.
// The push-buttons should be wired with pullup resistors (so the LOW means that the button is pressed)
u8g2.begin(/*Select/OK=*/ 7, /*Right/Next=*/ 4, /*Left/Prev=*/ 3, /*Up=*/ 5, /*Down=*/ 2, /*Home/Cancel=*/ 6);

Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!

Init and draw menu to the screen (Adafruit GFX version)

Generally in setup() function of the sketch:

// Use this initializer if using a 1.8" TFT screen:
tft.initR(INITR_BLACKTAB);      // Init ST7735S chip, black tab
// OR use this initializer if using a 1.8" TFT screen with offset such as WaveShare:
// tft.initR(INITR_GREENTAB);   // Init ST7735S chip, green tab
// See more options in Adafruit GFX library documentation

// Optionally, rotate display
// tft.setRotation(3); // See Adafruit GFX library documentation for details

// Menu init
menu.init();

// Menu setup: add menu items to menu pages to menu here (see below for examples)
menuPageMain.addMenuItem(menuItemByte);
menu.setMenuPageCurrent(menuPageMain);

// Menu draw
menu.drawMenu();

Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!

Enable Cyrillic

For U8g2 version of GEM only. You may use Cyrillic in menu title, menu item labels (including buttons and menu page links), and select options. Editable strings with Cyrillic characters are not supported (edit mode of such strings may lead to unpredictable results due to incompatibility with 2-byte characters). Increases required program storage space, use cautiously. By default Cyrillic typeset is off. To enable Cyrillic support, call enableCyrillic() method before init():

menu.enableCyrillic();

Related examples: Example 01: Basic

Create menu page

Create menu page labeled "Main Menu":

GEMPage menuPageMain("Main Menu");

Add menu page to menu and set it as current (generally in setup() function of the sketch, before menu.drawMenu() is called):

menu.setMenuPageCurrent(menuPageMain);

Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!

Create byte menu item

Define byte variable:

byte byteNumber = 42;

Create menu item linked to byteNumber variable and labeled "Byte":

GEMItem menuItemByteNumber("Byte:", byteNumber);

Add menu item to existing menu page (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemByteNumber);

Create int menu item

Define int variable:

int intNumber = -512;

Create menu item linked to intNumber variable and labeled "Int":

GEMItem menuItemIntNumber("Int:", intNumber);

Add menu item to existing menu page (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemIntNumber);

Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!

Create float menu item

Define float variable:

float floatNumber = 3.1416;

Create menu item linked to floatNumber variable and labeled "Float":

GEMItem menuItemFloatNumber("Float:", floatNumber);

Add menu item to existing menu page (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemFloatNumber);

By default, float variable is displayed with 6 digits after decimal point (and rounded accordingly if necessary), i.e. 3.141600. To change that, call GEMItem::setPrecision() method:

menuItemFloatNumber.setPrecision(3);

That will result in 3.142 being displayed as a value of the menu item (and that will overwrite original value of 3.1416 upon saving).

See Floating-point variables section of Readme for more details on float and double variables support.

Create double menu item

Define double variable:

double doubleNumber = -2.7183;

Create menu item linked to doubleNumber variable and labeled "Double":

GEMItem menuItemDoubleNumber("Double:", doubleNumber);

Add menu item to existing menu page (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemDoubleNumber);

By default, double variable is displayed with 6 digits after decimal point (and rounded accordingly if necessary), i.e. -2.718300. To change that, call GEMItem::setPrecision() method:

menuItemDoubleNumber.setPrecision(3);

That will result in -2.718 being displayed as a value of the menu item (and that will overwrite original value of -2.718 upon saving).

See Floating-point variables section of Readme for more details on float and double variables support.

Create bool menu item

Define bool variable:

bool flag = false;

Create menu item linked to flag variable and labeled "Bored?":

GEMItem menuItemFlag("Bored?", flag);

Add menu item to existing menu page (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemFlag);

Related examples: Example 01: Basic, Example 03: Party Hard!

Create char[17] (a.k.a. char[GEM_STR_LEN]) menu item

GEM_STR_LEN (equals 17) is the only length of the editable char array currently supported by GEM (keep in mind that the last element is reserved for terminating character, so the actual length available for user data is GEM_STR_LEN - 1 or 16). Using length different from GEM_STR_LEN may cause overflows and unpredicted behaviour. Define char[17] or char[GEM_STR_LEN] variable:

char name[GEM_STR_LEN] = "Jack Daniels";

Create menu item linked to name variable and labeled "Name":

GEMItem menuItemName("Name", name);

Add menu item to existing menu page (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemName);

Related examples: Example 02: Blink

Create byte option select

Define byte variable:

byte byteNumber = 42;

Create array of options:

SelectOptionByte optionsArray[] = {{"Opt 1", 10}, {"Opt 2", 42}, {"Opt 3", 115}};

Supply array of options to GEMSelect constructor (note that length of the array should be explicitly supplied/calculated because array is passed by reference):

GEMSelect myByteSelect(3, optionsArray);
// or
GEMSelect myByteSelect(sizeof(optionsArray)/sizeof(SelectOptionByte), optionsArray);
// or (no need to explicitly define optionsArray[] in this case)
GEMSelect myByteSelect(3, (SelectOptionByte[]){{"Opt 1", 10}, {"Opt 2", 42}, {"Opt 3", 115}});

Create menu item linked to byteNumber variable and to myByteSelect, and labeled "Number":

GEMItem menuItemByteNumber("Number:", byteNumber, myByteSelect);

Add menu item to existing menu page (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemByteNumber);

Related examples: Example 03: Party Hard!

Create int option select

Define int variable:

int intNumber = -512;

Create array of options:

SelectOptionInt optionsArray[] = {{"Opt 1", -512}, {"Opt 2", 0}, {"Opt 3", 512}};

Supply array of options to GEMSelect constructor (note that length of the array should be explicitly supplied/calculated because array is passed by reference):

GEMSelect myIntSelect(3, optionsArray);
// or
GEMSelect myIntSelect(sizeof(optionsArray)/sizeof(SelectOptionInt), optionsArray);
// or (no need to explicitly define optionsArray[] in this case)
GEMSelect myIntSelect(3, (SelectOptionInt[]){{"Opt 1", -512}, {"Opt 2", 0}, {"Opt 3", 512}});

Create menu item linked to intNumber variable and to myIntSelect, and labeled "Number":

GEMItem menuItemIntNumber("Number:", intNumber, myIntSelect);

Add menu item to existing menu page (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemIntNumber);

Create float option select

Define float variable:

float floatNumber = -3.14;

Create array of options:

SelectOptionFloat optionsArray[] = {{"Opt 1", -3.14}, {"Opt 2", 0}, {"Opt 3", 3.14}};

Supply array of options to GEMSelect constructor (note that length of the array should be explicitly supplied/calculated because array is passed by reference):

GEMSelect myFloatSelect(3, optionsArray);
// or
GEMSelect myFloatSelect(sizeof(optionsArray)/sizeof(SelectOptionFloat), optionsArray);
// or (no need to explicitly define optionsArray[] in this case)
GEMSelect myFloatSelect(3, (SelectOptionFloat[]){{"Opt 1", -3.14}, {"Opt 2", 0}, {"Opt 3", 3.14}});

Create menu item linked to floatNumber variable and to myFloatSelect, and labeled "Number":

GEMItem menuItemFloatNumber("Number:", floatNumber, myFloatSelect);

Add menu item to existing menu page (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemFloatNumber);

Create double option select

Define double variable:

double doubleNumber = -2.7183;

Create array of options:

SelectOptionDouble optionsArray[] = {{"Opt 1", -2.7183}, {"Opt 2", 0}, {"Opt 3", 2.7183}};

Supply array of options to GEMSelect constructor (note that length of the array should be explicitly supplied/calculated because array is passed by reference):

GEMSelect myDoubleSelect(3, optionsArray);
// or
GEMSelect myDoubleSelect(sizeof(optionsArray)/sizeof(SelectOptionDouble), optionsArray);
// or (no need to explicitly define optionsArray[] in this case)
GEMSelect myDoubleSelect(3, (SelectOptionDouble[]){{"Opt 1", -2.7183}, {"Opt 2", 0}, {"Opt 3", 2.7183}});

Create menu item linked to doubleNumber variable and to myDoubleSelect, and labeled "Number":

GEMItem menuItemDoubleNumber("Number:", doubleNumber, myDoubleSelect);

Add menu item to existing menu page (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemDoubleNumber);

Create char[n] option select

There is no restriction on the length of character array that can be used with option select. However, make sure that character array of associated with menu item variable (of type char[n]) is big enough to hold select option with the longest value to avoid overflows. Define char[n] variable:

char name[5] = "Alex";

Create array of options:

SelectOptionChar optionsArray[] = {{"Alexander", "Alex"}, {"Michael", "Mike"}, {"Peter", "Pete"}};

Supply array of options to GEMSelect constructor (note that length of the array should be explicitly supplied/calculated because array is passed by reference):

GEMSelect myNameSelect(3, optionsArray);
// or
GEMSelect myNameSelect(sizeof(optionsArray)/sizeof(SelectOptionChar), optionsArray);
// or (no need to explicitly define optionsArray[] in this case)
GEMSelect myNameSelect(3, (SelectOptionChar[]){{"Alexander", "Alex"}, {"Michael", "Mike"}, {"Peter", "Pete"}});

Create menu item linked to name variable and to myNameSelect, and labeled "Name":

GEMItem menuItemName("Name:", name, myNameSelect);

Add menu item to existing menu page (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemName);

Add menu item callback (basic)

You can define function that will be called when associated with menu item variable is successfully saved. You should forward-declare it before supplying it to menu item GEMItem constructor:

void validateInterval();
GEMItem menuItemInterval("Interval:", interval, validateInterval);

void loadPreset();
GEMItem menuItemName("Mode:", mode, myModeSelect, loadPreset);

Later in the sketch define this function:

void validateInterval() {
  // Do something, e.g. validate value of the variable and change it accordingly:
  if (interval < 0) {
    interval = 0;
  }
}

void loadPreset() {
  // Do something, e.g. print selected value to Serial Monitor:
  Serial.print("Mode option set: ");
  Serial.println(mode);
  // ...or apply predefined values to other variables
  // based on the selected option, thus creating sort of preset selector
  switch (mode) {
    case 0:
      interval = 120;
      break;
    case 1:
      interval = 500;
      break;
    case 2:
      interval = 1000;
      break;
  }
}

Related examples: Example 02: Blink, Example 03: Party Hard!

Add menu item callback (with callback arguments)

Callback function (that will be called when associated with menu item variable is successfully saved) optionally can expect argument of type GEMCallbackData to be passed to it when it is executed. In this case optional user-defined value of an argument can be specified. You should forward-declare callback function before supplying it to menu item GEMItem constructor:

const int defaultInterval = 100;
void validateInterval(GEMCallbackData callbackData);
GEMItem menuItemInterval("Interval:", interval, validateInterval, defaultInterval);  // defaultInterval is user-defined callback value

void loadPreset(GEMCallbackData callbackData);
GEMItem menuItemName("Mode:", mode, myModeSelect, loadPreset, "Mode option set: "); // String "Mode option set: " is user-defined callback value

Later in the sketch define this function:

void validateInterval(GEMCallbackData callbackData) {
  // Do something, e.g. validate value of the variable and change it accordingly
  // based on user-defined callback argument:
  if (interval < callbackData.valInt) {
    interval = callbackData.valInt;
  }
}

void loadPreset(GEMCallbackData callbackData) {
  // Do something, e.g. print selected value to Serial Monitor
  // after printing user-defined callback argument:
  Serial.print(callbackData.valChar);
  Serial.println(mode);
  // ...or apply predefined values to other variables
  // based on the selected option, thus creating sort of preset selector
  switch (mode) {
    case 0:
      interval = 120;
      break;
    case 1:
      interval = 500;
      break;
    case 2:
      interval = 1000;
      break;
  }
}

Make menu item readonly

Readonly menu items are marked with symbol ^ and won't allow user to interact with the associated variable (as well as activate readonly menu links or buttons). Of course, it still can be changed programmatically.

You can mark menu item as readonly during its creation, e.g.:

GEMItem menuItemByteNumber("Byte:", byteNumber, GEM_READONLY);
GEMItem menuItemName("Name:", name, myNameSelect, GEM_READONLY);
GEMItem menuItemButton("Print", printData, GEM_READONLY);
GEMItem menuItemSettings("Settings", menuPageSettings, GEM_READONLY);

Note that you cannot specify both readonly mode and callback (see above) in the same constructor. However, you can set readonly mode for menu item with (or without) callback explicitly later using GEMItem::setReadonly() method:

menuItemByteNumber.setReadonly(); // Enable readonly mode
menuItemIntNumber.setReadonly(true); // Enable readonly mode
menuItemName.setReadonly(false); // Disable readonly mode

To get current state of the menu item use GEMItem::getReadonly() method:

bool isReadonly = menuItemByteNumber.getReadonly();

You can use it to toggle readonly mode of the menu item by supplying its negated output directly to GEMItem::setReadonly() method:

menuItemByteNumber.setReadonly(!menuItemByteNumber.getReadonly());

Related examples: Example 03: Party Hard!

Hide or show menu item

Hidden menu items won't be printed to the screen the next time menu is drawn (and user won't be able to activate them via graphical interface of the menu). To hide or show menu item use the following methods:

menuItemByteNumber.hide(); // Hide
menuItemIntButton.hide(true); // Hide
menuItemPrintButton.hide(false); // Show
menuItemName.show(); // Show

To get current state of the menu item use GEMItem::getHidden() method:

bool isHidden = menuItemByteNumber.getHidden();

You can use it to toggle menu item visibility by supplying its negated output directly to GEMItem::hide() method:

menuItemByteNumber.hide(!menuItemByteNumber.getHidden());

Remove menu item

It is possible to remove menu item from menu page completely. Unlike GEMItem::hide(), this will unlink menu item from menu page, so it can be added to another (or the same) menu page later.

menuItemByteNumber.remove();

Add menu item at a specified position

By default menu item is added at the end of the list of menu items of the page (including hidden ones). It is possible to add menu item at a specified position (zero-based number from 0 to 255), which is passed a second argument to GEMPage::addMenuItem() method. The following will add menu item as a third menu item (out of total number of menu items of the menu page including hidden ones) of the Settings menu page:

menuPageSettings.addMenuItem(menuItemByteNumber, 2);

If it is required to consider only visible menu items (out of all), it is possible to pass flag GEM_ITEMS_VISIBLE (equals to false) as a third argument:

menuPageSettings.addMenuItem(menuItemByteNumber, 2, GEM_ITEMS_VISIBLE);
// or
menuPageSettings.addMenuItem(menuItemByteNumber, 2, false);

If position is set to 0 and menu page has parent menu page, menu item will be added at position 1 instead (i.e. as a second menu item, after built-in Back button).

Create button (basic)

To create menu button that will call certain user-defined function, you should first forward-declare this function before supplying it to menu item GEMItem constructor:

void printData();
GEMItem menuItemPrintButton("Print", printData);

Later in the sketch define this function:

void printData() {
  Serial.print("Number is: ");
  Serial.println(number);
}

Related examples: Example 01: Basic

Create button (with callback arguments)

Callback function (that will be called when button is pressed) optionally can expect argument of type GEMCallbackData to be passed to it when it is executed. In this case optional user-defined value of an argument can be specified. You should forward-declare callback function before supplying it to menu item GEMItem constructor:

void pressButton(GEMCallbackData callbackData);
GEMItem menuItemButton("Light Switch", pressButton, "light_switch_1"); // String "light_switch_1" is user-defined callback value

Later in the sketch define this function:

void pressButton(GEMCallbackData callbackData) {
  Serial.print("Button ID: ");
  Serial.print(callbackData.valChar); // Accessing user-defined callback argument
  Serial.print(" | Button title: ");
  Serial.println(callbackData.pMenuItem->getTitle()); // Accessing menu item title
}

Create button (advanced, with custom context)

To create menu button that will create new context (which can have its own enter (setup) and exit callbacks as well as loop function), you should first forward-declare initial function before supplying it to menu item GEMItem constructor:

void buttonAction();
GEMItem menuItemButton("Blink!", buttonAction);

Later in the sketch define this function:

void buttonAction() {
  // Declaration of context functions
  menu.context.loop = buttonContextLoop;
  menu.context.enter = buttonContextEnter; // Optional
  menu.context.exit = buttonContextExit; // Optional
  // Call of context functions
  menu.context.enter();
  //menu.context.allowExit = false; // Set to false if required to manually exit
                                    // the context loop from within the loop itself
                                    // (default is true)
}

Then define all context-related functions (optional enter and exit callbacks, and loop function):

void buttonContextEnter() {
  // Running some user-defined routines that usually belongs to setup()
  // Clear LCD screen
  glcd.clearScreen();
  // Initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

void buttonContextLoop() {
  // Do something in the loop() function you would normally do in the sketch,
  // e.g. blink built in LED
  digitalWrite(LED_BUILTIN, HIGH);
  delay(500);
  digitalWrite(LED_BUILTIN, LOW);
  delay(500);
}

void buttonContextExit() {
  // Set GEM specific settings to their values
  menu.reInit();
  // Draw menu back to screen
  menu.drawMenu();
  // Clear context (assigns `nullptr` values to function pointers
  // of the `context` property of the `GEM` object
  // and resets `allowExit` flag to its default state)
  menu.clearContext();
}

To exit currently running context and return to menu, press button associated with GEM_KEY_CANCEL key (only if context.allowExit flag is set to its default value of true, otherwise you should handle exit from the loop manually and call context.exit() explicitly) - context.exit() callback will be called.

Related examples: Example 02: Blink, Example 03: Party Hard!

Create submenu

Create additional menu page and menu item linked to this page:

GEMPage menuPageSettings("Settings");
GEMItem menuItemMainSettings("Settings", menuPageSettings);

Add menu item to existing menu page, where you'd like the link to the submenu to appear, and specify parent menu page, so that menu would know what page to return to when exiting submenu (generally in setup() function of the sketch, before menu.drawMenu() is called):

menuPageMain.addMenuItem(menuItemMainSettings);
menuPageSettings.setParentMenuPage(menuPageMain);

Alternatively it is possible to specify parent menu page during submenu object creation (since GEM ver. 1.4.6):

GEMPage menuPageSettings("Settings", menuPageMain);

If GEMPage::setParentMenuPage() is called additional time, previously added parent menu page will be overridden with the newly passed one.

Related examples: Example 02: Blink

Chain operations

It is possible to chain calls to some of the common methods of GEM, GEM_u8g2, GEM_adafruit_gfx, GEMPage, GEMItem objects (since GEM ver. 1.4.6):

menuPageSettings
  .addMenuItem(menuItemInterval)
  .addMenuItem(menuItemLabel)
  .setParentMenuPage(menuPageMain);

menu
  .reInit()
  .drawMenu()
  .clearContext();

Detect key presses with KeyDetector

KeyDetector library provides an easy way to detect single digital push-button presses (wired with pulldown resistor, so the HIGH means that the button is pressed). To make use of it, first install and include it into your sketch:

#include <KeyDetector.h>

Then define pins you want to detect signal on:

const byte downPin = 2;
const byte leftPin = 3;
const byte rightPin = 4;
const byte upPin = 5;
const byte cancelPin = 6;
const byte okPin = 7;

Create array of Key objects that will link GEM key identifiers with corresponding pin:

Key keys[] = {{GEM_KEY_UP, upPin}, {GEM_KEY_RIGHT, rightPin}, {GEM_KEY_DOWN, downPin}, {GEM_KEY_LEFT, leftPin}, {GEM_KEY_CANCEL, cancelPin}, {GEM_KEY_OK, okPin}};

Create KeyDetector object:

KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key));

To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay (in ms) as the third argument to KeyDetector constructor:

KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 10);

Specify push-buttons pin modes in setup() routine:

pinMode(downPin, INPUT);
pinMode(leftPin, INPUT);
pinMode(rightPin, INPUT);
pinMode(upPin, INPUT);
pinMode(cancelPin, INPUT);
pinMode(okPin, INPUT);

Modify the loop() function to include key detection routines as follows:

void loop() {
  // If menu is ready to accept button press...
  if (menu.readyForKey()) {
    // ...detect key press using KeyDetector library
    myKeyDetector.detect();
    // Pass pressed button to menu
    // (pressed button ID is stored in trigger property of KeyDetector object)
    menu.registerKeyPress(myKeyDetector.trigger);
  }
}

Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!

Detect key presses with U8g2

If you are using U8g2 version of GEM, you can utilize U8g2 library built-in key press event detection (although it is still possible to use other key detection methods as well). To use it, first supply u8g2.begin() method with pin numbers the push-buttons (wired with pullup resistor, so the LOW means that the button is pressed) are attached to:

u8g2.begin(/*Select/OK=*/ 7, /*Right/Next=*/ 4, /*Left/Prev=*/ 3, /*Up=*/ 5, /*Down=*/ 2, /*Home/Cancel=*/ 6);

Modify the loop() function to include press event detection through u8g2.getMenuEvent() method as follows:

void loop() {
  // If menu is ready to accept button press...
  if (menu.readyForKey()) {
    // ...detect key press using U8g2 library
    // and pass pressed button to menu
    menu.registerKeyPress(u8g2.getMenuEvent());
  }
}

Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!

Control menu from your sketch

To make menu perform requested operation (navigate, enter or exit edit mode, save variable, etc.) call menu.registerKeyPress() method with one of the GEM key identifiers as an argument:

menu.registerKeyPress(GEM_KEY_NONE); // Do nothing

menu.registerKeyPress(GEM_KEY_UP); // Navigate up through the menu items list,
                                   // select next value of the digit/char
                                   // of editable variable, or previous option in select

menu.registerKeyPress(GEM_KEY_RIGHT); // Navigate through the link to another (child) menu page,
                                      // select next digit/char of editable variable,
                                      // execute code associated with button

menu.registerKeyPress(GEM_KEY_DOWN); // Navigate down through the menu items list,
                                     // select previous value of the digit/char
                                     // of editable variable, or next option in select

menu.registerKeyPress(GEM_KEY_LEFT); // Navigate through the Back button to the previous menu page,
                                     // select previous digit/char of editable variable

menu.registerKeyPress(GEM_KEY_CANCEL); // Navigate to the previous (parent) menu page,
                                       // exit edit mode without saving the variable,
                                       // exit context loop if allowed within context's settings

menu.registerKeyPress(GEM_KEY_OK); // Toggle bool menu item, enter edit mode
                                   // of the associated non-bool variable,
                                   // exit edit mode with saving the variable,
                                   // execute code associated with button

Check out an example of controlling menu with keyboard via Serial (e.g. Arduino Serial Monitor).

Control menu with rotary encoder

Refer to Example 05: Encoder for possible implementation. Using menu.invertKeysDuringEdit() method to invert order in which characters loop when editing char[17] or number variables may be beneficial to user experience, e.g. rotating knob clock-wise is generally associated with GEM_KEY_DOWN action during navigation through menu items, but in edit mode it seems more natural to increment a digit rather than to decrement it when performing the same clock-wise rotation.

Related examples: Example 05: Encoder (AltSerialGraphicLCD, U8g2, Adafruit)

Disable floating-point support

It is possible to exclude support for editable float and double variables to save some space on your chip (up to 10% of program storage space on UNO). For that, locate file config.h that comes with the library, open it and comment out corresponding inclusion, i.e. change this line:

#include "config/support-float-edit.h"

to

// #include "config/support-float-edit.h"

Note that option selects support float and double variables regardless of this setting.

Additional examples

Collection of additional examples and tricks: