From 8517d5f91560cb3c6ee32f5220759ee535d64fb7 Mon Sep 17 00:00:00 2001
From: Marcio Teixeira <marcio@alephobjects.com>
Date: Mon, 5 Nov 2018 21:51:10 -0700
Subject: [PATCH] LCD menu code refactoring and cleanup (#12308)

---
 Marlin/src/lcd/menu/menu.cpp        | 116 ++++++-------
 Marlin/src/lcd/menu/menu.h          | 247 ++++++++++++++++------------
 Marlin/src/lcd/menu/menu_sdcard.cpp |  42 +++--
 Marlin/src/lcd/ultralcd.h           |   2 +-
 4 files changed, 218 insertions(+), 189 deletions(-)

diff --git a/Marlin/src/lcd/menu/menu.cpp b/Marlin/src/lcd/menu/menu.cpp
index 52b6573328..cd7e193fc6 100644
--- a/Marlin/src/lcd/menu/menu.cpp
+++ b/Marlin/src/lcd/menu/menu.cpp
@@ -106,10 +106,7 @@ void lcd_goto_previous_menu_no_defer() {
 /////////// Common Menu Actions ////////////
 ////////////////////////////////////////////
 
-void _menu_action_back() { lcd_goto_previous_menu(); }
-void menu_action_submenu(screenFunc_t func) { lcd_save_previous_screen(); lcd_goto_screen(func); }
-void menu_action_gcode(PGM_P pgcode) { enqueue_and_echo_commands_P(pgcode); }
-void menu_action_function(screenFunc_t func) { (*func)(); }
+void menu_item_gcode::action(PGM_P pgcode) { enqueue_and_echo_commands_P(pgcode); }
 
 ////////////////////////////////////////////
 /////////// Menu Editing Actions ///////////
@@ -118,76 +115,71 @@ void menu_action_function(screenFunc_t func) { (*func)(); }
 /**
  * Functions for editing single values
  *
- * The "DEFINE_MENU_EDIT_TYPE" macro generates the functions needed to edit a numerical value.
+ * The "DEFINE_MENU_EDIT_ITEM" macro generates the functions needed to edit a numerical value.
  *
- * For example, DEFINE_MENU_EDIT_TYPE(int16_t, int3, itostr3, 1) expands into these functions:
+ * The prerequisite is that in the header the type was already declared:
  *
- *   bool _menu_edit_int3();
- *   void menu_edit_int3(); // edit int16_t (interactively)
- *   void menu_edit_callback_int3(); // edit int16_t (interactively) with callback on completion
- *   void _menu_action_setting_edit_int3(PGM_P const pstr, int16_t * const ptr, const int16_t minValue, const int16_t maxValue);
- *   void menu_action_setting_edit_int3(PGM_P const pstr, int16_t * const ptr, const int16_t minValue, const int16_t maxValue);
- *   void menu_action_setting_edit_callback_int3(PGM_P const pstr, int16_t * const ptr, const int16_t minValue, const int16_t maxValue, const screenFunc_t callback, const bool live); // edit int16_t with callback
+ *   DECLARE_MENU_EDIT_TYPE(int16_t, int3, itostr3, 1)
+ *
+ * For example, DEFINE_MENU_EDIT_ITEM(int3) expands into these functions:
+ *
+ *   bool menu_item_int3::_edit();
+ *   void menu_item_int3::edit(); // edit int16_t (interactively)
+ *   void menu_item_int3::action_setting_edit(PGM_P const pstr, int16_t * const ptr, const int16_t minValue, const int16_t maxValue, const screenFunc_t callback = null, const bool live = false);
  *
  * You can then use one of the menu macros to present the edit interface:
  *   MENU_ITEM_EDIT(int3, MSG_SPEED, &feedrate_percentage, 10, 999)
  *
  * This expands into a more primitive menu item:
- *   MENU_ITEM(setting_edit_int3, MSG_SPEED, PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
+ *   MENU_ITEM_VARIANT(int3, _setting_edit, MSG_SPEED, PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
  *
  * ...which calls:
- *       menu_action_setting_edit_int3(PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
+ *       menu_item_int3::action_setting_edit(PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
  */
-#define DEFINE_MENU_EDIT_TYPE(TYPE, NAME, STRFUNC, SCALE) \
-  bool _menu_edit_ ## NAME() { \
-    ENCODER_DIRECTION_NORMAL(); \
-    if ((int32_t)encoderPosition < 0) encoderPosition = 0; \
-    if ((int32_t)encoderPosition > maxEditValue) encoderPosition = maxEditValue; \
-    if (lcdDrawUpdate) \
-      lcd_implementation_drawedit(editLabel, STRFUNC(((TYPE)((int32_t)encoderPosition + minEditValue)) * (1.0f / SCALE))); \
-    if (lcd_clicked || (liveEdit && lcdDrawUpdate)) { \
-      TYPE value = ((TYPE)((int32_t)encoderPosition + minEditValue)) * (1.0f / SCALE); \
-      if (editValue != NULL) *((TYPE*)editValue) = value; \
-      if (callbackFunc && (liveEdit || lcd_clicked)) (*callbackFunc)(); \
-      if (lcd_clicked) lcd_goto_previous_menu(); \
-    } \
-    return use_click(); \
-  } \
-  void menu_edit_ ## NAME() { _menu_edit_ ## NAME(); } \
-  void _menu_action_setting_edit_ ## NAME(PGM_P const pstr, TYPE* const ptr, const TYPE minValue, const TYPE maxValue) { \
-    lcd_save_previous_screen(); \
-    lcd_refresh(); \
-    \
-    editLabel = pstr; \
-    editValue = ptr; \
-    minEditValue = minValue * SCALE; \
-    maxEditValue = maxValue * SCALE - minEditValue; \
-    encoderPosition = (*ptr) * SCALE - minEditValue; \
-  } \
-  void menu_action_setting_edit_callback_ ## NAME(PGM_P const pstr, TYPE * const ptr, const TYPE minValue, const TYPE maxValue, const screenFunc_t callback/*=NULL*/, const bool live/*=false*/) { \
-    _menu_action_setting_edit_ ## NAME(pstr, ptr, minValue, maxValue); \
-    currentScreen = menu_edit_ ## NAME; \
-    callbackFunc = callback; \
-    liveEdit = live; \
-  } \
-  typedef void NAME##_void
+void menu_item_invariants::edit(strfunc_t strfunc, loadfunc_t loadfunc) {
+  ENCODER_DIRECTION_NORMAL();
+  if ((int32_t)encoderPosition < 0) encoderPosition = 0;
+  if ((int32_t)encoderPosition > maxEditValue) encoderPosition = maxEditValue;
+  if (lcdDrawUpdate)
+    lcd_implementation_drawedit(editLabel, strfunc(encoderPosition + minEditValue));
+  if (lcd_clicked || (liveEdit && lcdDrawUpdate)) {
+    if (editValue != NULL) loadfunc(editValue, encoderPosition + minEditValue);
+    if (callbackFunc && (liveEdit || lcd_clicked)) (*callbackFunc)();
+    if (lcd_clicked) lcd_goto_previous_menu();
+    lcd_clicked = false;
+  }
+}
 
-DEFINE_MENU_EDIT_TYPE(int16_t, int3, itostr3, 1);
-DEFINE_MENU_EDIT_TYPE(int16_t, int4, itostr4sign, 1);
-DEFINE_MENU_EDIT_TYPE(uint8_t, int8, i8tostr3, 1);
-DEFINE_MENU_EDIT_TYPE(float, float3, ftostr3, 1);
-DEFINE_MENU_EDIT_TYPE(float, float52, ftostr52, 100);
-DEFINE_MENU_EDIT_TYPE(float, float43, ftostr43sign, 1000);
-DEFINE_MENU_EDIT_TYPE(float, float5, ftostr5rj, 0.01f);
-DEFINE_MENU_EDIT_TYPE(float, float51, ftostr51sign, 10);
-DEFINE_MENU_EDIT_TYPE(float, float52sign, ftostr52sign, 100);
-DEFINE_MENU_EDIT_TYPE(float, float62, ftostr62rj, 100);
-DEFINE_MENU_EDIT_TYPE(uint32_t, long5, ftostr5rj, 0.01f);
+void menu_item_invariants::init(PGM_P const el, void * const ev, const int32_t minv, const int32_t maxv, const uint32_t ep, const screenFunc_t cs, const screenFunc_t cb, const bool le) {
+  lcd_save_previous_screen();
+  lcd_refresh();
+  editLabel = el;
+  editValue = ev;
+  minEditValue = minv;
+  maxEditValue = maxv;
+  encoderPosition = ep;
+  currentScreen = cs;
+  callbackFunc = cb;
+  liveEdit = le;
+}
 
-void menu_action_setting_edit_bool(PGM_P pstr, bool* ptr) { UNUSED(pstr); *ptr ^= true; lcd_refresh(); }
-void menu_action_setting_edit_callback_bool(PGM_P pstr, bool* ptr, screenFunc_t callback) {
-  menu_action_setting_edit_bool(pstr, ptr);
-  (*callback)();
+#define DEFINE_MENU_EDIT_ITEM(NAME) template class menu_item_template<NAME ## _item_info>;
+
+DEFINE_MENU_EDIT_ITEM(int3);
+DEFINE_MENU_EDIT_ITEM(int4);
+DEFINE_MENU_EDIT_ITEM(int8);
+DEFINE_MENU_EDIT_ITEM(float3);
+DEFINE_MENU_EDIT_ITEM(float52);
+DEFINE_MENU_EDIT_ITEM(float43);
+DEFINE_MENU_EDIT_ITEM(float5);
+DEFINE_MENU_EDIT_ITEM(float51);
+DEFINE_MENU_EDIT_ITEM(float52sign);
+DEFINE_MENU_EDIT_ITEM(float62);
+DEFINE_MENU_EDIT_ITEM(long5);
+
+void menu_item_bool::action_setting_edit(PGM_P pstr, bool *ptr, screenFunc_t callback) {
+  UNUSED(pstr); *ptr ^= true; lcd_refresh();
+  if (callback) (*callback)();
 }
 
 ////////////////////////////////////////////
diff --git a/Marlin/src/lcd/menu/menu.h b/Marlin/src/lcd/menu/menu.h
index fbbb00392b..cea49318a4 100644
--- a/Marlin/src/lcd/menu/menu.h
+++ b/Marlin/src/lcd/menu/menu.h
@@ -37,6 +37,30 @@ bool printer_busy();
 void lcd_completion_feedback(const bool good=true);
 void lcd_goto_previous_menu();
 void lcd_goto_previous_menu_no_defer();
+void lcd_save_previous_screen();
+
+////////////////////////////////////////////
+////////// Menu Item Numeric Types /////////
+////////////////////////////////////////////
+
+#define DECLARE_MENU_EDIT_TYPE(TYPE, NAME, STRFUNC, SCALE) \
+  struct NAME ## _item_info { \
+    typedef TYPE type_t; \
+    static constexpr float scale = SCALE; \
+    static inline char* strfunc(const float value) { return STRFUNC((TYPE) value); } \
+  };
+
+DECLARE_MENU_EDIT_TYPE(int16_t, int3, itostr3, 1);
+DECLARE_MENU_EDIT_TYPE(int16_t, int4, itostr4sign, 1);
+DECLARE_MENU_EDIT_TYPE(uint8_t, int8, i8tostr3, 1);
+DECLARE_MENU_EDIT_TYPE(float, float3, ftostr3, 1);
+DECLARE_MENU_EDIT_TYPE(float, float52, ftostr52, 100);
+DECLARE_MENU_EDIT_TYPE(float, float43, ftostr43sign, 1000);
+DECLARE_MENU_EDIT_TYPE(float, float5, ftostr5rj, 0.01f);
+DECLARE_MENU_EDIT_TYPE(float, float51, ftostr51sign, 10);
+DECLARE_MENU_EDIT_TYPE(float, float52sign, ftostr52sign, 100);
+DECLARE_MENU_EDIT_TYPE(float, float62, ftostr62rj, 100);
+DECLARE_MENU_EDIT_TYPE(uint32_t, long5, ftostr5rj, 0.01f);
 
 ////////////////////////////////////////////
 ///////// Menu Item Draw Functions /////////
@@ -54,7 +78,7 @@ void lcd_implementation_drawedit(const char* const pstr, const char* const value
 #endif
 #if HAS_GRAPHICAL_LCD
   void _drawmenu_setting_edit_generic(const bool isSelected, const uint8_t row, const char* pstr, const char* const data, const bool pgm);
-  #define lcd_implementation_drawmenu_back(sel, row, pstr, dummy) lcd_implementation_drawmenu_generic(sel, row, pstr, LCD_STR_UPLEVEL[0], LCD_STR_UPLEVEL[0])
+  #define lcd_implementation_drawmenu_back(sel, row, pstr) lcd_implementation_drawmenu_generic(sel, row, pstr, LCD_STR_UPLEVEL[0], LCD_STR_UPLEVEL[0])
   #define lcd_implementation_drawmenu_setting_edit_generic(sel, row, pstr, data) _drawmenu_setting_edit_generic(sel, row, pstr, data, false)
   #define lcd_implementation_drawmenu_setting_edit_generic_P(sel, row, pstr, data) _drawmenu_setting_edit_generic(sel, row, pstr, data, true)
   #define DRAWMENU_SETTING_EDIT_GENERIC(SRC) lcd_implementation_drawmenu_setting_edit_generic(sel, row, pstr, SRC)
@@ -68,7 +92,7 @@ void lcd_implementation_drawedit(const char* const pstr, const char* const value
     void _lcd_zoffset_overlay_gfx(const float zvalue);
   #endif
 #else
-  #define lcd_implementation_drawmenu_back(sel, row, pstr, dummy) lcd_implementation_drawmenu_generic(sel, row, pstr, LCD_UPLEVEL_CHAR, LCD_UPLEVEL_CHAR)
+  #define lcd_implementation_drawmenu_back(sel, row, pstr) lcd_implementation_drawmenu_generic(sel, row, pstr, LCD_UPLEVEL_CHAR, LCD_UPLEVEL_CHAR)
   void lcd_implementation_drawmenu_setting_edit_generic(const bool sel, const uint8_t row, const char* pstr, const char pre_char, const char* const data);
   void lcd_implementation_drawmenu_setting_edit_generic_P(const bool sel, const uint8_t row, const char* pstr, const char pre_char, const char* const data);
   #define DRAWMENU_SETTING_EDIT_GENERIC(SRC) lcd_implementation_drawmenu_setting_edit_generic(sel, row, pstr, '>', SRC)
@@ -90,75 +114,103 @@ void lcd_implementation_drawedit(const char* const pstr, const char* const value
 /////// Edit Setting Draw Functions ////////
 ////////////////////////////////////////////
 
-#define DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(TYPE, NAME, STRFUNC) \
+#define _DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(TYPE, NAME, STRFUNC) \
   FORCE_INLINE void lcd_implementation_drawmenu_setting_edit_ ## NAME (const bool sel, const uint8_t row, PGM_P pstr, PGM_P pstr2, TYPE * const data, ...) { \
     UNUSED(pstr2); \
     DRAWMENU_SETTING_EDIT_GENERIC(STRFUNC(*(data))); \
   } \
-  FORCE_INLINE void lcd_implementation_drawmenu_setting_edit_callback_ ## NAME (const bool sel, const uint8_t row, PGM_P pstr, PGM_P pstr2, TYPE * const data, ...) { \
-    UNUSED(pstr2); \
-    DRAWMENU_SETTING_EDIT_GENERIC(STRFUNC(*(data))); \
-  } \
   FORCE_INLINE void lcd_implementation_drawmenu_setting_edit_accessor_ ## NAME (const bool sel, const uint8_t row, PGM_P pstr, PGM_P pstr2, TYPE (*pget)(), void (*pset)(TYPE), ...) { \
     UNUSED(pstr2); UNUSED(pset); \
     DRAWMENU_SETTING_EDIT_GENERIC(STRFUNC(pget())); \
   } \
   typedef void NAME##_void
-DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(int16_t, int3, itostr3);
-DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(int16_t, int4, itostr4sign);
-DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(uint8_t, int8, i8tostr3);
-DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float3, ftostr3);
-DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float52, ftostr52);
-DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float43, ftostr43sign);
-DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float5, ftostr5rj);
-DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float51, ftostr51sign);
-DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float52sign, ftostr52sign);
-DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float62, ftostr62rj);
-DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(uint32_t, long5, ftostr5rj);
+#define DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(NAME) _DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(NAME ## _item_info::type_t, NAME, NAME ## _item_info::strfunc)
 
-#define lcd_implementation_drawmenu_setting_edit_bool(sel, row, pstr, pstr2, data)                    DRAW_BOOL_SETTING(sel, row, pstr, data)
-#define lcd_implementation_drawmenu_setting_edit_callback_bool(sel, row, pstr, pstr2, data, callback) DRAW_BOOL_SETTING(sel, row, pstr, data)
-#define lcd_implementation_drawmenu_setting_edit_accessor_bool(sel, row, pstr, pstr2, pget, pset)     DRAW_BOOL_SETTING(sel, row, pstr, data)
+DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(int3);
+DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(int4);
+DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(int8);
+DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float3);
+DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float52);
+DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float43);
+DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float5);
+DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float51);
+DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float52sign);
+DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float62);
+DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(long5);
+
+#define lcd_implementation_drawmenu_setting_edit_bool(sel, row, pstr, pstr2, data, ...)           DRAW_BOOL_SETTING(sel, row, pstr, data)
+#define lcd_implementation_drawmenu_setting_edit_accessor_bool(sel, row, pstr, pstr2, pget, pset) DRAW_BOOL_SETTING(sel, row, pstr, data)
 
 ////////////////////////////////////////////
 /////////////// Menu Actions ///////////////
 ////////////////////////////////////////////
 
-#define menu_action_back(dummy) _menu_action_back()
-void _menu_action_back();
-void menu_action_submenu(screenFunc_t data);
-void menu_action_function(menuAction_t data);
-void menu_action_gcode(const char* pgcode);
+class menu_item_back {
+  public:
+    static inline void action() { lcd_goto_previous_menu(); }
+};
+
+class menu_item_submenu {
+  public:
+    static inline void action(const screenFunc_t func) { lcd_save_previous_screen(); lcd_goto_screen(func); }
+};
+
+class menu_item_gcode {
+  public:
+    static void action(const char * const pgcode);
+};
+
+class menu_item_function {
+  public:
+    static inline void action(const menuAction_t func) { (*func)(); };
+};
 
 ////////////////////////////////////////////
 /////////// Menu Editing Actions ///////////
 ////////////////////////////////////////////
 
-#define DECLARE_MENU_EDIT_TYPE(TYPE, NAME) \
-  bool _menu_edit_ ## NAME(); \
-  void menu_edit_ ## NAME(); \
-  void menu_edit_callback_ ## NAME(); \
-  void _menu_action_setting_edit_ ## NAME(PGM_P const pstr, TYPE* const ptr, const TYPE minValue, const TYPE maxValue); \
-  void menu_action_setting_edit_callback_ ## NAME(PGM_P const pstr, TYPE * const ptr, const TYPE minValue, const TYPE maxValue, const screenFunc_t callback=NULL, const bool live=false); \
-  FORCE_INLINE void menu_action_setting_edit_ ## NAME(PGM_P const pstr, TYPE * const ptr, const TYPE minValue, const TYPE maxValue) { \
-    menu_action_setting_edit_callback_ ## NAME(pstr, ptr, minValue, maxValue); \
-  } \
-  typedef void NAME##_void
+class menu_item_invariants {
+  protected:
+    typedef char* (*strfunc_t)(const int32_t);
+    typedef void (*loadfunc_t)(void *, const int32_t);
+    static void init(PGM_P const el, void * const ev, const int32_t minv, const int32_t maxv, const uint32_t ep, const screenFunc_t cs, const screenFunc_t cb, const bool le);
+    static void edit(strfunc_t, loadfunc_t);
+};
 
-DECLARE_MENU_EDIT_TYPE(int16_t, int3);
-DECLARE_MENU_EDIT_TYPE(int16_t, int4);
-DECLARE_MENU_EDIT_TYPE(uint8_t, int8);
-DECLARE_MENU_EDIT_TYPE(float, float3);
-DECLARE_MENU_EDIT_TYPE(float, float52);
-DECLARE_MENU_EDIT_TYPE(float, float43);
-DECLARE_MENU_EDIT_TYPE(float, float5);
-DECLARE_MENU_EDIT_TYPE(float, float51);
-DECLARE_MENU_EDIT_TYPE(float, float52sign);
-DECLARE_MENU_EDIT_TYPE(float, float62);
-DECLARE_MENU_EDIT_TYPE(uint32_t, long5);
+template<typename NAME>
+class menu_item_template : menu_item_invariants {
+  private:
+    typedef typename NAME::type_t type_t;
+    inline static float unscale(const float value)    {return value * (1.0f / NAME::scale);}
+    inline static float scale(const float value)      {return value * NAME::scale;}
+    static void  load(void *ptr, const int32_t value) {*((type_t*)ptr) = unscale(value);}
+    static char* to_string(const int32_t value)       {return NAME::strfunc(unscale(value));}
+  public:
+    static void action_setting_edit(PGM_P const pstr, type_t * const ptr, const type_t minValue, const type_t maxValue, const screenFunc_t callback=NULL, const bool live=false) {
+      const int32_t minv = scale(minValue);
+      init(pstr, ptr, minv, int32_t(scale(maxValue)) - minv, int32_t(scale(*ptr)) - minv, edit, callback, live);
+    }
+    static void edit() {menu_item_invariants::edit(to_string, load);}
+};
 
-void menu_action_setting_edit_bool(PGM_P pstr, bool* ptr);
-void menu_action_setting_edit_callback_bool(PGM_P pstr, bool* ptr, screenFunc_t callbackFunc);
+#define DECLARE_MENU_EDIT_ITEM(NAME) typedef menu_item_template<NAME ## _item_info> menu_item_ ## NAME;
+
+DECLARE_MENU_EDIT_ITEM(int3);
+DECLARE_MENU_EDIT_ITEM(int4);
+DECLARE_MENU_EDIT_ITEM(int8);
+DECLARE_MENU_EDIT_ITEM(float3);
+DECLARE_MENU_EDIT_ITEM(float52);
+DECLARE_MENU_EDIT_ITEM(float43);
+DECLARE_MENU_EDIT_ITEM(float5);
+DECLARE_MENU_EDIT_ITEM(float51);
+DECLARE_MENU_EDIT_ITEM(float52sign);
+DECLARE_MENU_EDIT_ITEM(float62);
+DECLARE_MENU_EDIT_ITEM(long5);
+
+class menu_item_bool {
+  public:
+    static void action_setting_edit(PGM_P pstr, bool* ptr, const screenFunc_t callbackFunc=NULL);
+};
 
 ////////////////////////////////////////////
 //////////// Menu System Macros ////////////
@@ -226,67 +278,63 @@ void menu_action_setting_edit_callback_bool(PGM_P pstr, bool* ptr, screenFunc_t
 
   extern bool encoderRateMultiplierEnabled;
   #define ENCODER_RATE_MULTIPLY(F) (encoderRateMultiplierEnabled = F)
-
+  #define _MENU_ITEM_MULTIPLIER_CHECK(USE_MULTIPLIER) if(USE_MULTIPLIER) {encoderRateMultiplierEnabled = true; lastEncoderMovementMillis = 0;}
   //#define ENCODER_RATE_MULTIPLIER_DEBUG  // If defined, output the encoder steps per second value
-
-  /**
-   * MENU_MULTIPLIER_ITEM generates drawing and handling code for a multiplier menu item
-   */
-  #define MENU_MULTIPLIER_ITEM(TYPE, LABEL, ...) do { \
-      _MENU_ITEM_PART_1(TYPE, ## __VA_ARGS__); \
-      encoderRateMultiplierEnabled = true; \
-      lastEncoderMovementMillis = 0; \
-      _MENU_ITEM_PART_2(TYPE, PSTR(LABEL), ## __VA_ARGS__); \
-    }while(0)
-
 #else // !ENCODER_RATE_MULTIPLIER
   #define ENCODER_RATE_MULTIPLY(F) NOOP
+  #define _MENU_ITEM_MULTIPLIER_CHECK(USE_MULTIPLIER)
 #endif // !ENCODER_RATE_MULTIPLIER
 
 /**
  * MENU_ITEM generates draw & handler code for a menu item, potentially calling:
  *
- *   lcd_implementation_drawmenu_[type](sel, row, label, arg3...)
- *   menu_action_[type](arg3...)
+ *   lcd_implementation_drawmenu_<type>[_variant](sel, row, label, arg3...)
+ *   menu_item_<type>::action[_variant](arg3...)
  *
  * Examples:
  *   MENU_ITEM(back, MSG_WATCH, 0 [dummy parameter] )
  *   or
  *   MENU_BACK(MSG_WATCH)
  *     lcd_implementation_drawmenu_back(sel, row, PSTR(MSG_WATCH))
- *     menu_action_back()
+ *     menu_item_back::action()
  *
  *   MENU_ITEM(function, MSG_PAUSE_PRINT, lcd_sdcard_pause)
  *     lcd_implementation_drawmenu_function(sel, row, PSTR(MSG_PAUSE_PRINT), lcd_sdcard_pause)
- *     menu_action_function(lcd_sdcard_pause)
+ *     menu_item_function::action(lcd_sdcard_pause)
  *
  *   MENU_ITEM_EDIT(int3, MSG_SPEED, &feedrate_percentage, 10, 999)
- *   MENU_ITEM(setting_edit_int3, MSG_SPEED, PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
  *     lcd_implementation_drawmenu_setting_edit_int3(sel, row, PSTR(MSG_SPEED), PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
- *     menu_action_setting_edit_int3(PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
+ *     menu_item_int3::action_setting_edit(PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
  *
  */
-#define _MENU_ITEM_PART_1(TYPE, ...) \
-  if (_menuLineNr == _thisItemNr) { \
-    if (encoderLine == _thisItemNr && lcd_clicked) { \
-      lcd_clicked = false
+#define _MENU_ITEM_VARIANT_P(TYPE, VARIANT, USE_MULTIPLIER, PLABEL, ...) do { \
+    _skipStatic = false; \
+    if (_menuLineNr == _thisItemNr) { \
+      if (encoderLine == _thisItemNr && lcd_clicked) { \
+        lcd_clicked = false; \
+        _MENU_ITEM_MULTIPLIER_CHECK(USE_MULTIPLIER); \
+        menu_item_ ## TYPE ::action ## VARIANT(__VA_ARGS__); \
+        if (screen_changed) return; \
+      } \
+      if (lcdDrawUpdate) \
+        lcd_implementation_drawmenu ## VARIANT ## _ ## TYPE(encoderLine == _thisItemNr, _lcdLineNr, PLABEL, ## __VA_ARGS__); \
+    } \
+  ++_thisItemNr; \
+}while(0)
 
-#define _MENU_ITEM_PART_2(TYPE, PLABEL, ...) \
-      menu_action_ ## TYPE(__VA_ARGS__); \
-      if (screen_changed) return; \
+// Used to print static text with no visible cursor.
+// Parameters: label [, bool center [, bool invert [, char *value] ] ]
+#define STATIC_ITEM_P(PLABEL, ...) do{ \
+  if (_menuLineNr == _thisItemNr) { \
+    if (_skipStatic && encoderLine <= _thisItemNr) { \
+      encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \
+      ++encoderLine; \
     } \
     if (lcdDrawUpdate) \
-      lcd_implementation_drawmenu_ ## TYPE(encoderLine == _thisItemNr, _lcdLineNr, PLABEL, ## __VA_ARGS__); \
+      lcd_implementation_drawmenu_static(_lcdLineNr, PLABEL, ## __VA_ARGS__); \
   } \
-  ++_thisItemNr
-
-#define MENU_ITEM_P(TYPE, PLABEL, ...) do { \
-    _skipStatic = false; \
-    _MENU_ITEM_PART_1(TYPE, ## __VA_ARGS__); \
-    _MENU_ITEM_PART_2(TYPE, PLABEL, ## __VA_ARGS__); \
-  }while(0)
-
-#define MENU_ITEM(TYPE, LABEL, ...) MENU_ITEM_P(TYPE, PSTR(LABEL), ## __VA_ARGS__)
+  ++_thisItemNr; \
+} while(0)
 
 #define MENU_ITEM_ADDON_START(X) \
   if (lcdDrawUpdate && _menuLineNr == _thisItemNr - 1) { \
@@ -294,33 +342,16 @@ void menu_action_setting_edit_callback_bool(PGM_P pstr, bool* ptr, screenFunc_t
 
 #define MENU_ITEM_ADDON_END() } (0)
 
-#define MENU_BACK(LABEL) MENU_ITEM(back, LABEL, 0)
-
-// Used to print static text with no visible cursor.
-// Parameters: label [, bool center [, bool invert [, char *value] ] ]
-#define STATIC_ITEM_P(LABEL, ...) do{ \
-  if (_menuLineNr == _thisItemNr) { \
-    if (_skipStatic && encoderLine <= _thisItemNr) { \
-      encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \
-      ++encoderLine; \
-    } \
-    if (lcdDrawUpdate) \
-      lcd_implementation_drawmenu_static(_lcdLineNr, LABEL, ## __VA_ARGS__); \
-  } \
-  ++_thisItemNr; } while(0)
-
 #define STATIC_ITEM(LABEL, ...) STATIC_ITEM_P(PSTR(LABEL), ## __VA_ARGS__)
 
+#define MENU_BACK(LABEL) MENU_ITEM(back, LABEL)
 #define MENU_ITEM_DUMMY() do { _thisItemNr++; }while(0)
-#define MENU_ITEM_EDIT(TYPE, LABEL, ...) MENU_ITEM(_CAT(setting_edit_,TYPE), LABEL, PSTR(LABEL), ## __VA_ARGS__)
-#define MENU_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...) MENU_ITEM(_CAT(setting_edit_callback_,TYPE), LABEL, PSTR(LABEL), ## __VA_ARGS__)
-#if ENABLED(ENCODER_RATE_MULTIPLIER)
-  #define MENU_MULTIPLIER_ITEM_EDIT(TYPE, LABEL, ...) MENU_MULTIPLIER_ITEM(_CAT(setting_edit_,TYPE), LABEL, PSTR(LABEL), ## __VA_ARGS__)
-  #define MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...) MENU_MULTIPLIER_ITEM(_CAT(setting_edit_callback_,TYPE), LABEL, PSTR(LABEL), ## __VA_ARGS__)
-#else // !ENCODER_RATE_MULTIPLIER
-  #define MENU_MULTIPLIER_ITEM_EDIT(TYPE, LABEL, ...) MENU_ITEM(_CAT(setting_edit_,TYPE), LABEL, PSTR(LABEL), ## __VA_ARGS__)
-  #define MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...) MENU_ITEM(_CAT(setting_edit_callback_,TYPE), LABEL, PSTR(LABEL), ## __VA_ARGS__)
-#endif // !ENCODER_RATE_MULTIPLIER
+#define MENU_ITEM_P(TYPE, PLABEL, ...)                       _MENU_ITEM_VARIANT_P(TYPE,              , 0, PLABEL,                   ## __VA_ARGS__)
+#define MENU_ITEM(TYPE, LABEL, ...)                          _MENU_ITEM_VARIANT_P(TYPE,              , 0, PSTR(LABEL),              ## __VA_ARGS__)
+#define MENU_ITEM_EDIT(TYPE, LABEL, ...)                     _MENU_ITEM_VARIANT_P(TYPE, _setting_edit, 0, PSTR(LABEL), PSTR(LABEL), ## __VA_ARGS__)
+#define MENU_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...)            _MENU_ITEM_VARIANT_P(TYPE, _setting_edit, 0, PSTR(LABEL), PSTR(LABEL), ## __VA_ARGS__)
+#define MENU_MULTIPLIER_ITEM_EDIT(TYPE, LABEL, ...)          _MENU_ITEM_VARIANT_P(TYPE, _setting_edit, 1, PSTR(LABEL), PSTR(LABEL), ## __VA_ARGS__)
+#define MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...) _MENU_ITEM_VARIANT_P(TYPE, _setting_edit, 1, PSTR(LABEL), PSTR(LABEL), ## __VA_ARGS__)
 
 ////////////////////////////////////////////
 /////////////// Menu Screens ///////////////
diff --git a/Marlin/src/lcd/menu/menu_sdcard.cpp b/Marlin/src/lcd/menu/menu_sdcard.cpp
index 1384ce515a..94f1957089 100644
--- a/Marlin/src/lcd/menu/menu_sdcard.cpp
+++ b/Marlin/src/lcd/menu/menu_sdcard.cpp
@@ -71,25 +71,31 @@ void lcd_sd_updir() {
   }
 #endif
 
-void menu_action_sdfile(CardReader &theCard) {
-  #if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
-    last_sdfile_encoderPosition = encoderPosition;  // Save which file was selected for later use
-  #endif
-  card.openAndPrintFile(theCard.filename);
-  lcd_return_to_status();
-  lcd_reset_status();
-}
+class menu_item_sdfile {
+  public:
+    static void action(CardReader &theCard) {
+      #if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
+        last_sdfile_encoderPosition = encoderPosition;  // Save which file was selected for later use
+      #endif
+      card.openAndPrintFile(theCard.filename);
+      lcd_return_to_status();
+      lcd_reset_status();
+    }
+};
 
-void menu_action_sddirectory(CardReader &theCard) {
-  card.chdir(theCard.filename);
-  encoderTopLine = 0;
-  encoderPosition = 2 * ENCODER_STEPS_PER_MENU_ITEM;
-  screen_changed = true;
-  #if HAS_GRAPHICAL_LCD
-    drawing_screen = false;
-  #endif
-  lcd_refresh();
-}
+class menu_item_sddirectory {
+  public:
+    static void action(CardReader &theCard) {
+      card.chdir(theCard.filename);
+      encoderTopLine = 0;
+      encoderPosition = 2 * ENCODER_STEPS_PER_MENU_ITEM;
+      screen_changed = true;
+      #if HAS_GRAPHICAL_LCD
+        drawing_screen = false;
+      #endif
+      lcd_refresh();
+    }
+};
 
 void menu_sdcard() {
   ENCODER_DIRECTION_MENUS();
diff --git a/Marlin/src/lcd/ultralcd.h b/Marlin/src/lcd/ultralcd.h
index 49cb32fdcf..8216e3761a 100644
--- a/Marlin/src/lcd/ultralcd.h
+++ b/Marlin/src/lcd/ultralcd.h
@@ -279,7 +279,7 @@
     typedef void (*screenFunc_t)();
     typedef void (*menuAction_t)();
     extern screenFunc_t currentScreen;
-    void lcd_goto_screen(screenFunc_t screen, const uint32_t encoder=0);
+    void lcd_goto_screen(const screenFunc_t screen, const uint32_t encoder=0);
 
     extern bool lcd_clicked, defer_return_to_status;