Learning path for UI Menu design in arduino

I've just roughed this out real quick.... But I've had relative success in the past a linked lists approach... (well, multi-linked lists anyway!).

Something approximating this:

#include "KTS_Button.h"

#include <LiquidCrystal_I2C.h>

KTS_Button buttons[] = { 22, 23, 24, 25, 9 };
const byte NUM_BTNS = sizeof(buttons) / sizeof(buttons[0]);

LiquidCrystal_I2C lcd(0x27, 16, 2);

class MenuItem {
  public:
    const char * title;
    MenuItem *parent = nullptr;
    MenuItem *prev = nullptr;
    MenuItem *next = nullptr;
    MenuItem *child = nullptr;

    MenuItem(const char * title)
      : title(title) {}

    void display() {
      lcd.clear();
      if (parent) {
        lcd.setCursor(0, 0);
        lcd.print("# ");
        lcd.print(parent->title);
        lcd.print(" #");
      }
      lcd.setCursor(0, 1);
      lcd.print(title);
    }
};

MenuItem *currentMenu;

MenuItem mainMenu = { "Main Menu" };
MenuItem l1_MenuOne = { "1) Menu One L1" }; // Level One Menu Item
MenuItem l1_MenuTwo = { "2) Menu Two L1" };
MenuItem l2_MenuOne = { "1) Menu One L2" };  // Level Two Menu Item
MenuItem l2_MenuTwo = { "2) Menu Two L2" };

void setup() {
  Serial.begin(115600);
  lcd.init();
  lcd.backlight();

  mainMenu.child = &l1_MenuOne;

  l1_MenuOne.parent = &mainMenu;
  l1_MenuOne.next = &l1_MenuTwo;

  l1_MenuTwo.prev = &l1_MenuOne;
  l1_MenuTwo.parent = &mainMenu;
  l1_MenuTwo.child = &l2_MenuOne;

  l2_MenuOne.parent = &l1_MenuTwo;
  l2_MenuOne.next = &l2_MenuTwo;
  l2_MenuTwo.prev = &l2_MenuOne;
  l2_MenuTwo.parent = &l1_MenuTwo;

  currentMenu = &mainMenu;
  currentMenu->display();
}

void handleMenuInput(byte buttonPressed) {
  switch (buttonPressed) {
    case 0:
      if (currentMenu->child)
        currentMenu = currentMenu->child;
      break;

    case 1:
      if (currentMenu->parent)
        currentMenu = currentMenu->parent;
      break;

    case 2:
      if (currentMenu->prev)
        currentMenu = currentMenu->prev;
      break;

    case 3:
      if (currentMenu->next)
        currentMenu = currentMenu->next;
      break;
  }
  currentMenu->display();
}

void loop() {
  for (byte i = 0; i < NUM_BTNS; i++) {
    if (buttons[i].read() == SINGLE_PRESS)
      handleMenuInput(i);
  }
}

The issues I've had so far are:

One, it's a bit of a waste to hold empty pointers where they are not needed (especially if you have a very large menu!) and two, writing out how everything is connected is messy, and very prone to typos/mistakes.

Adding function pointers or values and things to the menu items is pretty easy though! And it's a tested and working example. (The button class is my own, but any will do).