-
Notifications
You must be signed in to change notification settings - Fork 36
How to
The following is a small reference on how to do common tasks when creating a menu:
- Create menu object
- Init and draw menu to the screen
- Enable Cyrillic
- Create menu page
- Create
byte
menu item - Create
int
menu item - Create
float
menu item - Create
double
menu item - Create
bool
menu item - Create
char[17]
(a.k.a.char[GEM_STR_LEN]
) menu item - Create
byte
option select - Create
int
option select - Create
float
option select - Create
double
option select - Create
char[n]
option select - Add menu item callback (basic)
- Add menu item callback (with callback arguments)
- Make menu item readonly
- Hide or show menu item
- Remove menu item
- Add menu item at a specified position
- Create button (basic)
- Create button (with callback arguments)
- Create button (advanced, with custom context)
- Create submenu
- Chain operations
- Detect key presses with
KeyDetector
- Detect key presses with
U8g2
- Control menu from your sketch
- Control menu with rotary encoder
- Disable floating-point support
- Additional examples
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!
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!
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!
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!
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!
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!
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 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!
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);
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!
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
anddouble
variables support.
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
anddouble
variables support.
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!
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
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!
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);
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);
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);
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);
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!
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;
}
}
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!
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());
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();
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).
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
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
}
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 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
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();
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!
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!
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).
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)
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.
Collection of additional examples and tricks:
-
test_34_gem-u8g2_auto-load.ino
Modification of the Blink example. Loads Blink routine as soon as sketch starts. -
test_38_gem_dynamic-title.ino
Temporary change title of the button upon pressing it. -
test_42_gem-agfx_loop-creation.ino
Create menu items with user-defined callback argument in a loop. -
Control GEM via Serial
Example of controlling menu with keyboard via Serial (e.g. Arduino Serial Monitor). -
Example-01_Basic_Feather.ino
Modification of the Basic example to work with Adafruit Feather M0 board equipped with 128x64 OLED FeatherWing. Three of the available buttons are mapped toGEM_KEY_OK
(BUTTON_A),GEM_KEY_CANCEL
(BUTTON_B) andGEM_KEY_DOWN
(BUTTON_C). Should be enough for basic navigation (although without an ability to properly edit variables).
- Test bench (push-buttons):
- Example 01: Basic
- Example 02: Blink
- Example 03: Party Hard!
- Test bench (rotary encoder):
- Example 05: Encoder
- Example 06: Todo List