I'm trying to make a navigational menu with sub menus to display on an OLED. Have managed this OK, however I would like to display the next item on the same level, to encourage scrolling, but I am unable to figure this out.
I want to have it so that the current menu item is displayed, along with the next item one would reach if you scrolled downwards. I know you can display the previous and current items easily with
Serial.print(fromFlash(changed.from.getName()));
and
Serial.println(fromFlash(changed.to.getName()));
respectively - but I would like to display the current and the next (below but on same level) item. How can I do this? I played around for most of today on this but am pretty stumped.
I've simplified the example given which is below. Below that, I've included menubackend.h
Running on an ATMEGA328P chip.
#include "MenuBackend.h"
/*
This is the structure of the modelled menu
Settings
Pin
Debug
Options
Delay (D)
100 ms
200 ms
300 ms
400 ms
*/
#define TEST_FROMFLASH
#undef TEST_GETITEMNAMEFROMFLASH
const char itemNameSettings[] PROGMEM = "Settings";
const char itemNamePin[] PROGMEM = "Pin";
const char itemNameDebug[] PROGMEM = "Debug";
const char itemNameOptions[] PROGMEM = "Options";
const char itemNameDelay[] PROGMEM = "Delay";
const char itemName100ms[] PROGMEM = "100 ms";
const char itemName200ms[] PROGMEM = "200 ms";
const char itemName300ms[] PROGMEM = "300 ms";
const char itemName400ms[] PROGMEM = "400 ms";
const char itemNameshutdown[] PROGMEM = "shutdown";
static void menuChangeEvent(MenuChangeEvent changed);
static void menuUseEvent(MenuUseEvent used);
//this controls the menu backend and the event generation
MenuBackend menu = MenuBackend(menuUseEvent,menuChangeEvent);
//beneath is list of menu items needed to build the menu
MenuItem settings = MenuItem(itemNameSettings);
MenuItem pin = MenuItem(itemNamePin);
MenuItem debug = MenuItem(itemNameDebug);
MenuItem options = MenuItem(itemNameOptions);
MenuItem setDelay = MenuItem(itemNameDelay,'D');
MenuItem d100 = MenuItem(itemName100ms);
MenuItem d200 = MenuItem(itemName200ms);
MenuItem d300 = MenuItem(itemName300ms);
MenuItem d400 = MenuItem(itemName400ms);
MenuItem shutdown1 = MenuItem(itemNameshutdown);
//this function builds the menu and connects the correct items together
void menuSetup()
{
Serial.println(F("Setting up menu..."));
//add the file menu to the menu root
menu.getRoot().add(settings);
//setup the settings menu item
settings.addRight(pin);
//we want looping both up and down
pin.addBefore(debug);
pin.addAfter(debug);
debug.addAfter(pin);
//we want a left movement to pint to settings from anywhere
debug.addLeft(shutdown1);
pin.addLeft(settings);
settings.addAfter(options);
shutdown1.addAfter(settings);
options.addAfter(shutdown1);
options.addRight(setDelay);
setDelay.addLeft(options);
setDelay.addRight(d100);
d100.addBefore(d100); //loop to d400
d100.addAfter(d200);
d200.addAfter(d300);
d300.addAfter(d400);
d400.addAfter(d100); //loop back to d100
//we want left to always be bak to delay
d100.addLeft(setDelay);
d200.addLeft(setDelay);
d300.addLeft(setDelay);
d400.addLeft(setDelay);
}
#ifdef TEST_FROMFLASH
inline const __FlashStringHelper * fromFlash(const char * pStr)
{
return reinterpret_cast<const __FlashStringHelper *>(pStr);
}
#endif
#ifdef TEST_GETITEMNAMEFROMFLASH
inline char * getItemNameFromFlash(const MenuItem & item, char * copyToBuffer, size_t bufferSize)
{
strncpy_P(copyToBuffer, item.getName(), bufferSize);
copyToBuffer[bufferSize - 1] = '\0'; // Make sure the buffer is null-terminated.
return copyToBuffer;
}
#endif
/*
This is an important function
Here all use events are handled
This is where you define a behaviour for a menu item
*/
void menuUseEvent(MenuUseEvent used)
{
Serial.print(F("Menu use "));
#ifdef TEST_FROMFLASH
Serial.println(fromFlash(used.item.getName()));
#endif
#ifdef TEST_GETITEMNAMEFROMFLASH
char itemNameBuffer[31]; // 30 chars + '\0';
Serial.println(getItemNameFromFlash(used.item, itemNameBuffer, sizeof(itemNameBuffer)));
#endif
if (used.item == setDelay) //comparison against a known item
{
Serial.println(F("menuUseEvent found Delay (D)"));
}
}
/*
This is an important function
Here we get a notification whenever the user changes the menu
That is, when the menu is navigated
*/
void menuChangeEvent(MenuChangeEvent changed)
{
Serial.print("Menu change ");
#ifdef TEST_FROMFLASH
Serial.println(fromFlash(changed.to.getName()));
#endif
#ifdef TEST_GETITEMNAMEFROMFLASH
char itemNameBuffer[31]; // 30 chars + '\0';
Serial.print(getItemNameFromFlash(changed.from, itemNameBuffer, sizeof(itemNameBuffer)));
Serial.print(" char is ");
Serial.println(getItemNameFromFlash(changed.to, itemNameBuffer, sizeof(itemNameBuffer)));
#endif
}
void setup()
{
Serial.begin(9600);
Serial.println(F("Revision 1.0."));
menuSetup();
Serial.println(F("Starting navigation:\r\n Down: s Left: a Right: d Use: e"));
}
void showAllNames()
{
#ifdef TEST_FROMFLASH
Serial.println(fromFlash(settings.getName()));
Serial.println(fromFlash(pin.getName()));
Serial.println(fromFlash(debug.getName()));
Serial.println(fromFlash(options.getName()));
Serial.println(fromFlash(setDelay.getName()));
Serial.println(fromFlash(d100.getName()));
Serial.println(fromFlash(d200.getName()));
Serial.println(fromFlash(d300.getName()));
Serial.println(fromFlash(d400.getName()));
#endif
#ifdef TEST_GETITEMNAMEFROMFLASH
char itemNameBuffer[31]; // 30 chars + '\0';
Serial.println(getItemNameFromFlash(settings, itemNameBuffer, sizeof(itemNameBuffer)));
Serial.println(getItemNameFromFlash(pin, itemNameBuffer, sizeof(itemNameBuffer)));
Serial.println(getItemNameFromFlash(debug, itemNameBuffer, sizeof(itemNameBuffer)));
Serial.println(getItemNameFromFlash(options, itemNameBuffer, sizeof(itemNameBuffer)));
Serial.println(getItemNameFromFlash(setDelay, itemNameBuffer, sizeof(itemNameBuffer)));
Serial.println(getItemNameFromFlash(d100, itemNameBuffer, sizeof(itemNameBuffer)));
Serial.println(getItemNameFromFlash(d200, itemNameBuffer, sizeof(itemNameBuffer)));
Serial.println(getItemNameFromFlash(d300, itemNameBuffer, sizeof(itemNameBuffer)));
Serial.println(getItemNameFromFlash(d400, itemNameBuffer, sizeof(itemNameBuffer)));
#endif
}
void loop()
{
if (Serial.available()) {
byte read = Serial.read();
switch (read) {
case 's':
menu.moveDown();
break;
case 'd':
menu.moveRight();
break;
case 'a':
menu.moveLeft();
break;
case 'e':
menu.use();
break;
case 'z':
showAllNames();
break;
}
}
}
MenuBackend.h
/*
||
|| @file MenuBackend.h
|| @version 1.4
|| @author Alexander Brevig
|| @contact [email protected]
|| @contribution Adrian Brzezinski [email protected], http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?action=viewprofile;username=vzhang
||
|| @description
|| | Provide an easy way of making menus
|| #
||
|| @license
|| | This library is free software; you can redistribute it and/or
|| | modify it under the terms of the GNU Lesser General Public
|| | License as published by the Free Software Foundation; version
|| | 2.1 of the License.
|| |
|| | This library is distributed in the hope that it will be useful,
|| | but WITHOUT ANY WARRANTY; without even the implied warranty of
|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|| | Lesser General Public License for more details.
|| |
|| | You should have received a copy of the GNU Lesser General Public
|| | License along with this library; if not, write to the Free Software
|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|| #
||
*/
#ifndef MenuBackend_h
#define MenuBackend_h
#include <avr/pgmspace.h>
/*
A menu item will be a container for an item that is a part of a menu
Each such item has a logical position in the hierarchy as well as a text and maybe a mnemonic shortkey
*/
class MenuItem {
public:
MenuItem(const char * itemName, char shortKey='\0' ) : name(itemName), shortkey(shortKey) {
before = right = after = left = 0;
}
//void use(){} //update some internal data / statistics
inline const char * getName() const { return name; }
inline const char getShortkey() const { return shortkey; }
inline const bool hasShortkey() const { return (shortkey!='\0'); }
inline void setBack(MenuItem *b) { back = b; }
inline MenuItem* getBack() const { return back; }
inline MenuItem* getBefore() const { return before; }
inline MenuItem* getRight() const { return right; }
inline MenuItem* getAfter() const { return after; }
inline MenuItem* getLeft() const { return left; }
MenuItem *moveBack() { return back; }
MenuItem *moveUp() {
if (before) { before->back = this; }
return before;
}
MenuItem *moveDown() {
if (after) { after->back = this; }
return after;
}
MenuItem *moveLeft() {
if (left) { left->back = this; }
return left;
}
MenuItem *moveRight() {
if (right) { right->back = this; }
return right;
}
//default vertical menu
MenuItem &add(MenuItem &mi) { return addAfter(mi); }
MenuItem &addBefore(MenuItem &mi) {
mi.after = this;
before = &mi;
if ( !mi.back ) mi.back = back;
return mi;
}
MenuItem &addRight(MenuItem &mi) {
mi.left = this;
right = &mi;
if ( !mi.back ) mi.back = back;
return mi;
}
MenuItem &addAfter(MenuItem &mi) {
mi.before = this;
after = &mi;
if ( !mi.back ) mi.back = back;
return mi;
}
MenuItem &addLeft(MenuItem &mi) {
mi.right = this;
left = &mi;
if ( !mi.back ) mi.back = back;
return mi;
}
protected:
const char * name;
const char shortkey;
MenuItem *before;
MenuItem *right;
MenuItem *after;
MenuItem *left;
MenuItem *back;
};
//no dependant inclusion of string or cstring
bool menuTestStrings1(const char *a, const char *b) { // a is in flash, b is in SRAM.
for (uint8_t da = pgm_read_byte_near(a); da; da = pgm_read_byte_near(++a), b++)
{
if (da != *b)
return false;
}
return (*b == '\0'); // We reached the end of a. Strings are equal only if we reached the end of b too.
}
bool menuTestStrings2(const char *a, const char *b) { // a and b are both in flash.
uint8_t da = pgm_read_byte_near(a); // Dereferenced a.
uint8_t db = pgm_read_byte_near(b); // Dereferenced b.
for ( ; da; da = pgm_read_byte_near(++a), db = pgm_read_byte_near(++b))
{
if (da != db)
return false;
}
return (db == '\0'); // We reached the end of a. Strings are equal only if we reached the end of b too.
}
bool operator==(MenuItem &lhs, char* test) {
return menuTestStrings1(lhs.getName(),test);
}
bool operator==(const MenuItem &lhs, char* test) {
return menuTestStrings1(lhs.getName(),test);
}
bool operator==(MenuItem &lhs, MenuItem &rhs) {
return menuTestStrings2(lhs.getName(),rhs.getName());
}
bool operator==(const MenuItem &lhs, MenuItem &rhs) {
return menuTestStrings2(lhs.getName(),rhs.getName());
}
struct MenuChangeEvent {
const MenuItem &from;
const MenuItem &to;
};
struct MenuUseEvent {
const MenuItem &item;
};
typedef void (*cb_change)(MenuChangeEvent);
typedef void (*cb_use)(MenuUseEvent);
const char itemNameMenuRoot[] PROGMEM = "MenuRoot";
class MenuBackend {
public:
MenuBackend(cb_use menuUse, cb_change menuChange = 0) : root(itemNameMenuRoot) {
current = &root;
cb_menuChange = menuChange;
cb_menuUse = menuUse;
}
MenuItem &getRoot() {
return root;
}
MenuItem &getCurrent() {
return *current;
}
void moveBack() {
setCurrent(current->getBack());
}
void moveUp() {
setCurrent(current->moveUp());
}
void moveDown() {
setCurrent(current->moveDown());
}
void moveLeft() {
setCurrent(current->moveLeft());
}
void moveRight() {
setCurrent(current->moveRight());
}
void use(char shortkey)
{
recursiveSearch(shortkey,&root);
use();
}
void use() {
//current->use();
if (cb_menuUse) {
MenuUseEvent mue = { *current };
cb_menuUse(mue);
}
}
private:
void setCurrent( MenuItem *next ) {
if (next) {
if (cb_menuChange) {
MenuChangeEvent mce = { *current, *next };
(*cb_menuChange)(mce);
}
current = next;
}
}
void foundShortkeyItem(MenuItem *mi) {
mi->setBack(current);
current = mi;
}
char canSearch(const char shortkey, MenuItem *m) {
if (m==0) { return 0; }
else {
if (m->getShortkey()==shortkey) {
foundShortkeyItem(m);
return 1;
}
return -1;
}
}
void rSAfter(const char shortkey, MenuItem *m) {
if (canSearch(shortkey,m)!=1) {
rSAfter(shortkey, m->getAfter());
rSRight(shortkey, m->getRight());
rSLeft(shortkey, m->getLeft());
}
}
void rSRight(const char shortkey, MenuItem *m) {
if (canSearch(shortkey,m)!=1) {
rSAfter(shortkey, m->getAfter());
rSRight(shortkey, m->getRight());
rSBefore(shortkey, m->getBefore());
}
}
void rSLeft(const char shortkey, MenuItem *m) {
if (canSearch(shortkey,m)!=1) {
rSAfter(shortkey, m->getAfter());
rSLeft(shortkey, m->getLeft());
rSBefore(shortkey, m->getBefore());
}
}
void rSBefore(const char shortkey, MenuItem *m) {
if (canSearch(shortkey,m)!=1) {
rSRight(shortkey, m->getRight());
rSLeft(shortkey, m->getLeft());
rSBefore(shortkey, m->getBefore());
}
}
void recursiveSearch(const char shortkey, MenuItem *m) {
if (canSearch(shortkey,m)!=1) {
rSAfter(shortkey, m->getAfter());
rSRight(shortkey, m->getRight());
rSLeft(shortkey, m->getLeft());
rSBefore(shortkey, m->getBefore());
}
}
MenuItem root;
MenuItem *current;
cb_change cb_menuChange;
cb_use cb_menuUse;
};
#endif