/*
example in how to implement the menu library for and ESP32 and Adafruit libraries. This is an example that
uses both menu types
1) a simple selection menu (ItemMenu) where the user can scroll up/down and select
an item and some action be performed such as drawing a screen or activating a sub menu
2) an menu with in-line where the user can scroll up/down the list and select an item
however the difference is that move/up down on a selected item will scroll the values of that
menu item and NOT scroll the list, selecing a selected item will restor control to moving up/down
3) a EditMenu menu with in-line an no icons (down and dirty)
highlights
1. menu items exceeds screen size but library handles wrapping
2. each item selection has a range, increment and decimal readout
3. items can also be read from a char array
ESP32 display
3v3 VCC
GND GND
5 TFT_CS
25 TFT_RESET
2 TFT_DC
23 MOSI
18 SCK
3v3 LED
19 MISO
ESP32 Encoder
32 select button 1
GND select button 2
27 encoder direction 1
33 encoder direction 2
GND encoder dir ground
*/
// required libraries
#include "Adafruit_GFX.h"
#include <GEM_adafruit_gfx.h>
#include <KeyDetector.h>
// found in \Arduino\libraries\Adafruit-GFX-Library-master
#include "FreeSans18pt7b.h"
#include "FreeSans12pt7b.h"
#include "FreeSansBold12pt7b.h"
#include "FreeSansBold9pt7b.h"
#include "FreeSans9pt7b.h"
#include <ESP32Encoder.h> // https://github.com/madhephaestus/ESP32Encoder
#define ROW_HEIGHT 35
#define ROWS 5
#define DATA_COLUMN 200
#define TFT_DC 2
#define TFT_CS 15
#define TFT_RST 4
#define LED_PIN 26
#define EN1_PIN 27
#define EN2_PIN 33
#define SE_PIN 32
// easy way to include fonts but change globally
#define FONT_SMALL FreeSans9pt7b // font for menus
#define FONT_EDITTITLE FreeSans18pt7b // font for menus
#define FONT_ITEM FreeSans12pt7b // font for menus
#define FONT_TITLE FreeSans18pt7b // font for all headings
// Create variables that will be editable through the menu and assign them initial values
int interval = 200;
bool strobe = false;
// Create variable that will be editable through option select and create associated option select
byte tempo = 0;
SelectOptionByte selectTempoOptions[] = {{"Meh:(", 0}, {"Smooth", 1}, {"Hard", 2}, {"XTREME", 3}, {"Manual", 4}, {"Custom", 5}};
GEMSelect selectTempo(sizeof(selectTempoOptions)/sizeof(SelectOptionByte), selectTempoOptions);
// Values of interval variable associated with each select option
int tempoInterval[] = {400, 250, 120, 100, 0, 200};
// Supplementary variables used for animation control
unsigned long previousMillis = 0;
byte framesCount = 5;
byte currentFrame = framesCount;
// Create menu item objects of class GEMItem, linked to interval and strobe variables
// with validateInterval() callback function attached to interval menu item,
// that will make sure that interval variable is within allowable range (i.e. >= 0)
void validateInterval(); // Forward declaration
GEMItem menuItemInt("Interval:", interval, validateInterval);
GEMItem menuItemStrobe("Strobe:", strobe);
// Create menu item for option select with applyTempo() callback function
void applyTempo(); // Forward declaration
GEMItem menuItemTempo("Tempo:", tempo, selectTempo, applyTempo);
// Create menu button that will trigger rock() function. It will run animation sequence.
// We will write (define) this function later. However, we should
// forward-declare it in order to pass to GEMItem constructor
void rock(); // Forward declaration
GEMItem menuItemButton("Let's Rock!", rock);
// Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level.
// Menu can have multiple menu pages (linked to each other) with multiple menu items each
GEMPage menuPageMain("Party Hard");
// Create menu object of class GEM_adafruit_gfx. Supply its constructor with reference to tft object we created earlier
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);
void setup() {
// Push-buttons pin modes
pinMode(downPin, INPUT);
pinMode(leftPin, INPUT);
pinMode(rightPin, INPUT);
pinMode(upPin, INPUT);
pinMode(cancelPin, INPUT);
pinMode(okPin, INPUT);
// Serial communications setup
Serial.begin(115200);
// 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
// Load initial preset selected through tempo option select
applyTempo();
// Menu init, setup and draw
menu.init();
setupMenu();
menu.drawMenu();
}
void setupMenu() {
// Add menu items to menu page
menuPageMain.addMenuItem(menuItemTempo);
menuPageMain.addMenuItem(menuItemInt);
menuPageMain.addMenuItem(menuItemStrobe);
menuPageMain.addMenuItem(menuItemButton);
// Add menu page to menu and set it as current
menu.setMenuPageCurrent(menuPageMain);
}
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);
}
}
// ---
// Validation routine of interval variable
void validateInterval() {
// Check if interval variable is within allowable range (i.e. >= 0)
if (interval < 0) {
interval = 0;
}
// Print interval variable to Serial
Serial.print("Interval: ");
Serial.println(interval);
}
// Apply preset based on tempo variable value
void applyTempo() {
if (tempo != 5) {
// Set readonly mode for interval menu item
menuItemInt.setReadonly();
// Apply interval value based on preset selection
interval = tempoInterval[tempo];
// Turn on strobe effect for "XTREME" preset
strobe = tempo == 3;
} else {
// Disable readonly mode of interval menu item for "Custom" preset
menuItemInt.setReadonly(false);
}
// Print tempo variable to Serial
Serial.print("Tempo: ");
Serial.println(tempo);
}
// --- Animation draw routines
// Draw sprite on screen
// (note that Splash is the custom type used internally in GEM library for convenient way of storing bitmap graphics of Splash screen)
void drawSprite(Splash _splash, byte _mode) {
tft.fillScreen(_mode == 1 ? 0x0000 : 0xFFFF);
tft.drawBitmap((tft.width() - _splash.width) / 2, (tft.height() - _splash.height) / 2, _splash.image, _splash.width, _splash.height, _mode == 1 ? 0xFFFF : 0x0000);
}
// Draw frame based on direction of animation
void drawFrame(bool forward) {
if (forward) {
// Next frame
currentFrame = (currentFrame == framesCount ? 1 : currentFrame+1);
} else {
// Previous frame
currentFrame = (currentFrame == 1 ? framesCount : currentFrame-1);
}
// Set inversed mode based on strobe effect and frame number
byte mode = strobe && (currentFrame % 2 == 0) ? 0 : 1;
// Draw frame on screen
drawSprite(frames[currentFrame-1], mode);
}
// --- Animation context routines
// Setup context
void rock() {
menu.context.loop = rockContextLoop;
menu.context.enter = rockContextEnter;
menu.context.exit = rockContextExit;
menu.context.allowExit = false; // Setting to false will require manual exit from the loop
menu.context.enter();
}
// Invoked once when the button is pressed
void rockContextEnter() {
// Clear sreen
tft.fillScreen(0x0000);
// Draw initial frame for the case of manual navigation ("Manual" tempo preset)
if (interval == 0) {
drawFrame(true);
}
Serial.println("Partying hard!");
}
// Invoked every loop iteration
void rockContextLoop() {
// Detect key press manually using KeyDetector library
myKeyDetector.detect();
if (myKeyDetector.trigger == GEM_KEY_CANCEL) {
// Exit animation routine if GEM_KEY_CANCEL key was pressed
menu.context.exit();
} else {
if (interval > 0) {
// Autoplay mode.
// Test millis timer and draw frame accordingly
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
drawFrame(true);
}
} else {
// Manual mode.
// Check pressed keys and navigate through frames accordingly
switch (myKeyDetector.trigger) {
case GEM_KEY_RIGHT:
Serial.println("Next");
drawFrame(true);
break;
case GEM_KEY_LEFT:
Serial.println("Previous");
drawFrame(false);
break;
}
}
}
}
// Invoked once when the GEM_KEY_CANCEL key is pressed
void rockContextExit() {
// Reset variables
previousMillis = 0;
currentFrame = framesCount;
// Draw menu back on screen and clear context
menu.reInit();
menu.drawMenu();
menu.clearContext();
}