Broke my code using PROGMEM

  1. One the menu is drawn, don't redraw it unless you change something. So draw and wait.

Better option: draw menu and wait in a while() doing nothing on user input
Best option: don't make blocking code. Let the loop() loop. And when you want to display the menu, draw it and return to the loop() and remember the menu is open. If there is user input, act on it (redraw (parts), go to other menu etc) but return to the loop() again.

  1. You know try to use the global variable menu to print the menu on the display. But there is just one place you put anything in menu and that's in setup().
 for (int i = 0; i < 4; i++)  //Build menu table
  {
    strcpy_P(menu, (char*)pgm_read_word(&(menu_table[i]))); // Necessary casts and dereferencing, just copy.
    Serial.println(menu);
   delay( 250 );
  }

And each loop of that for-loop will start at the beginning of menu. So after the first loop it holds "Messaging", after the second "UV Scanner", after the third "Security" and after the last "Main Menu". So when you enter the loop() all menu contains is "Main Menu". Simply, don't use the menu varaible in your program, it's not needed and be declaring it the memory saving of PROGMEM is undone because it will consume 50 bytes...

Larp-fx:
Now that I can print the arrays using flashstringhelper, I assume that there's a better way to construct the menu tree.

Yes, the same way you printed to Serial :wink:

display.println((__FlashStringHelper*)menu_table[0]); //etc
//or
display.println(PtoF(menu_table[0]));