From a0c795b097a30eff006c8dff178abf5f1f1907fa Mon Sep 17 00:00:00 2001
From: Scott Lahteine <thinkyhead@users.noreply.github.com>
Date: Sun, 11 Nov 2018 12:16:24 -0600
Subject: [PATCH] Encapsulate common display code in a singleton (#12395)

* Encapsulate common LCD code in a singleton
* Depend more UBL code on UBL_DEVEL_DEBUGGING
  - Since most users don't need the debugging on at all times, this helps reduce the default build size for UBL by over 2K, a little closer to fitting on 128K boards.
---
 Marlin/src/Marlin.cpp                         |  14 +-
 .../examples/Geeetech/GT2560/Configuration.h  |   2 +-
 Marlin/src/feature/bedlevel/bedlevel.cpp      |   2 +-
 Marlin/src/feature/bedlevel/ubl/ubl.cpp       |  32 +-
 Marlin/src/feature/bedlevel/ubl/ubl.h         |  16 +-
 Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp   | 680 +++++++++---------
 Marlin/src/feature/pause.cpp                  |   2 +-
 Marlin/src/gcode/bedlevel/G26.cpp             |  57 +-
 Marlin/src/gcode/bedlevel/abl/G29.cpp         |   4 +-
 Marlin/src/gcode/bedlevel/mbl/G29.cpp         |   4 +-
 Marlin/src/gcode/calibrate/G28.cpp            |   2 +-
 Marlin/src/gcode/calibrate/G33.cpp            |   8 +-
 Marlin/src/gcode/control/M17_M18_M84.cpp      |   2 +-
 Marlin/src/gcode/control/M80_M81.cpp          |   2 +-
 Marlin/src/gcode/control/M999.cpp             |   2 +-
 Marlin/src/gcode/lcd/M0_M1.cpp                |   6 +-
 Marlin/src/gcode/lcd/M117.cpp                 |   2 +-
 Marlin/src/gcode/lcd/M145.cpp                 |   8 +-
 Marlin/src/gcode/lcd/M250.cpp                 |   6 +-
 Marlin/src/gcode/lcd/M73.cpp                  |   6 +-
 Marlin/src/gcode/motion/G4.cpp                |   2 +-
 Marlin/src/gcode/stats/M31.cpp                |   2 +-
 Marlin/src/gcode/temperature/M104_M109.cpp    |   4 +-
 Marlin/src/gcode/temperature/M140_M190.cpp    |   2 +-
 Marlin/src/inc/Conditionals_LCD.h             |  85 +--
 Marlin/src/inc/Conditionals_post.h            |  36 +
 Marlin/src/inc/SanityCheck.h                  |   2 +
 .../src/lcd/HD44780/ultralcd_common_HD44780.h |  66 +-
 .../src/lcd/HD44780/ultralcd_impl_HD44780.cpp | 246 +++----
 Marlin/src/lcd/dogm/status_screen_DOGM.cpp    |  81 +--
 .../lcd/dogm/status_screen_lite_ST7920.cpp    | 244 +++----
 .../dogm/status_screen_lite_ST7920_class.h    |   4 +-
 Marlin/src/lcd/dogm/ultralcd_impl_DOGM.cpp    |  57 +-
 Marlin/src/lcd/extensible_ui/ui_api.cpp       |  28 +-
 Marlin/src/lcd/language/language_es.h         |   2 +-
 Marlin/src/lcd/language/language_zh_CN.h      |   2 +-
 Marlin/src/lcd/language/language_zh_TW.h      |   2 +-
 Marlin/src/lcd/malyanlcd.cpp                  |   6 +-
 Marlin/src/lcd/menu/menu.cpp                  | 131 ++--
 Marlin/src/lcd/menu/menu.h                    | 151 ++--
 Marlin/src/lcd/menu/menu_advanced.cpp         |  12 +-
 Marlin/src/lcd/menu/menu_bed_corners.cpp      |   8 +-
 Marlin/src/lcd/menu/menu_bed_leveling.cpp     |  60 +-
 Marlin/src/lcd/menu/menu_configuration.cpp    |  26 +-
 Marlin/src/lcd/menu/menu_custom.cpp           |   4 +-
 Marlin/src/lcd/menu/menu_delta_calibrate.cpp  |  12 +-
 Marlin/src/lcd/menu/menu_filament.cpp         |  16 +-
 Marlin/src/lcd/menu/menu_info.cpp             |   8 +-
 Marlin/src/lcd/menu/menu_job_recovery.cpp     |   6 +-
 Marlin/src/lcd/menu/menu_main.cpp             |   8 +-
 Marlin/src/lcd/menu/menu_motion.cpp           |  58 +-
 Marlin/src/lcd/menu/menu_sdcard.cpp           |  57 +-
 Marlin/src/lcd/menu/menu_temperature.cpp      |  80 +--
 Marlin/src/lcd/menu/menu_tune.cpp             |  22 +-
 Marlin/src/lcd/menu/menu_ubl.cpp              |  62 +-
 Marlin/src/lcd/ultralcd.cpp                   | 537 +++++++-------
 Marlin/src/lcd/ultralcd.h                     | 602 ++++++++++------
 Marlin/src/libs/buzzer.h                      | 169 ++---
 Marlin/src/module/configuration_store.cpp     |  85 ++-
 Marlin/src/module/endstops.cpp                |   2 +-
 Marlin/src/module/motion.cpp                  |   4 +-
 Marlin/src/module/probe.cpp                   |   8 +-
 Marlin/src/module/temperature.cpp             |  20 +-
 Marlin/src/module/tool_change.cpp             |   2 +-
 Marlin/src/sd/cardreader.cpp                  |   8 +-
 65 files changed, 1885 insertions(+), 2001 deletions(-)

diff --git a/Marlin/src/Marlin.cpp b/Marlin/src/Marlin.cpp
index 944c5ec96f..3d77548ee2 100644
--- a/Marlin/src/Marlin.cpp
+++ b/Marlin/src/Marlin.cpp
@@ -371,7 +371,7 @@ void manage_inactivity(const bool ignore_stepper_queue/*=false*/) {
       #if HAS_LCD_MENU && ENABLED(AUTO_BED_LEVELING_UBL)
         if (ubl.lcd_map_control) {
           ubl.lcd_map_control = false;
-          set_defer_return_to_status(false);
+          ui.defer_status_screen(false);
         }
       #endif
     }
@@ -549,7 +549,7 @@ void idle(
     max7219.idle_tasks();
   #endif
 
-  lcd_update();
+  ui.update();
 
   #if ENABLED(HOST_KEEPALIVE_FEATURE)
     gcode.host_keepalive();
@@ -609,8 +609,8 @@ void kill(PGM_P const lcd_msg/*=NULL*/) {
   SERIAL_ERROR_START();
   SERIAL_ERRORLNPGM(MSG_ERR_KILLED);
 
-  #if ENABLED(ULTRA_LCD) || ENABLED(EXTENSIBLE_UI)
-    kill_screen(lcd_msg ? lcd_msg : PSTR(MSG_KILLED));
+  #if HAS_SPI_LCD || ENABLED(EXTENSIBLE_UI)
+    ui.kill_screen(lcd_msg ? lcd_msg : PSTR(MSG_KILLED));
   #else
     UNUSED(lcd_msg);
   #endif
@@ -899,11 +899,11 @@ void setup() {
     fanmux_init();
   #endif
 
-  lcd_init();
-  lcd_reset_status();
+  ui.init();
+  ui.reset_status();
 
   #if ENABLED(SHOW_BOOTSCREEN)
-    lcd_bootscreen();
+    ui.show_bootscreen();
   #endif
 
   #if ENABLED(MIXING_EXTRUDER)
diff --git a/Marlin/src/config/examples/Geeetech/GT2560/Configuration.h b/Marlin/src/config/examples/Geeetech/GT2560/Configuration.h
index f992c4b773..5c19c578b7 100644
--- a/Marlin/src/config/examples/Geeetech/GT2560/Configuration.h
+++ b/Marlin/src/config/examples/Geeetech/GT2560/Configuration.h
@@ -2035,7 +2035,7 @@
  */
 #if ENABLED(ULTIMAKERCONTROLLER) || ENABLED(REPRAP_DISCOUNT_SMART_CONTROLLER) || ENABLED(G3D_PANEL) || ENABLED(MKS_MINI_12864)
   #define SDSUPPORT   // Force SD Card support on for these displays
-#else
+#elif DISABLED(LIGHTWEIGHT_UI)
   #define LCD_WIDTH_OVERRIDE 20 // Default is 22. For this Geeetech use 20.
 #endif
 
diff --git a/Marlin/src/feature/bedlevel/bedlevel.cpp b/Marlin/src/feature/bedlevel/bedlevel.cpp
index 737a971fb0..bc5bf2397d 100644
--- a/Marlin/src/feature/bedlevel/bedlevel.cpp
+++ b/Marlin/src/feature/bedlevel/bedlevel.cpp
@@ -233,7 +233,7 @@ void reset_bed_level() {
     current_position[Y_AXIS] = ry;
 
     #if ENABLED(LCD_BED_LEVELING)
-      lcd_wait_for_move = false;
+      ui.wait_for_bl_move = false;
     #endif
   }
 
diff --git a/Marlin/src/feature/bedlevel/ubl/ubl.cpp b/Marlin/src/feature/bedlevel/ubl/ubl.cpp
index bc5088f209..c5ff87466a 100644
--- a/Marlin/src/feature/bedlevel/ubl/ubl.cpp
+++ b/Marlin/src/feature/bedlevel/ubl/ubl.cpp
@@ -34,8 +34,6 @@
 
   #include "math.h"
 
-  uint8_t ubl_cnt = 0;
-
   void unified_bed_leveling::echo_name(
     #if NUM_SERIAL > 1
       const int8_t port/*= -1*/
@@ -106,30 +104,19 @@
 
       if (xy_dist == 0.0) return;
 
-      SERIAL_ECHOPGM("   fpmm=");
       const float fpmm = de / xy_dist;
-      SERIAL_ECHO_F(fpmm, 6);
-
+      SERIAL_ECHOPGM("   fpmm="); SERIAL_ECHO_F(fpmm, 6);
       SERIAL_ECHOPGM("    current=( ");
-      SERIAL_ECHO_F(current_position[X_AXIS], 6);
-      SERIAL_ECHOPGM(", ");
-      SERIAL_ECHO_F(current_position[Y_AXIS], 6);
-      SERIAL_ECHOPGM(", ");
-      SERIAL_ECHO_F(current_position[Z_AXIS], 6);
-      SERIAL_ECHOPGM(", ");
-      SERIAL_ECHO_F(current_position[E_AXIS], 6);
-      SERIAL_ECHOPGM(" )   destination=( ");
-      debug_echo_axis(X_AXIS);
-      SERIAL_ECHOPGM(", ");
-      debug_echo_axis(Y_AXIS);
-      SERIAL_ECHOPGM(", ");
-      debug_echo_axis(Z_AXIS);
-      SERIAL_ECHOPGM(", ");
-      debug_echo_axis(E_AXIS);
-      SERIAL_ECHOPGM(" )   ");
+      SERIAL_ECHO_F(current_position[X_AXIS], 6); SERIAL_ECHOPGM(", ");
+      SERIAL_ECHO_F(current_position[Y_AXIS], 6); SERIAL_ECHOPGM(", ");
+      SERIAL_ECHO_F(current_position[Z_AXIS], 6); SERIAL_ECHOPGM(", ");
+      SERIAL_ECHO_F(current_position[E_AXIS], 6); SERIAL_ECHOPGM(" )   destination=( ");
+      debug_echo_axis(X_AXIS); SERIAL_ECHOPGM(", ");
+      debug_echo_axis(Y_AXIS); SERIAL_ECHOPGM(", ");
+      debug_echo_axis(Z_AXIS); SERIAL_ECHOPGM(", ");
+      debug_echo_axis(E_AXIS); SERIAL_ECHOPGM(" )   ");
       serialprintPGM(title);
       SERIAL_EOL();
-
     }
 
   #endif // UBL_DEVEL_DEBUGGING
@@ -150,7 +137,6 @@
   volatile int unified_bed_leveling::encoder_diff;
 
   unified_bed_leveling::unified_bed_leveling() {
-    ubl_cnt++;  // Debug counter to ensure we only have one UBL object present in memory.  We can eliminate this (and all references to ubl_cnt) very soon.
     reset();
   }
 
diff --git a/Marlin/src/feature/bedlevel/ubl/ubl.h b/Marlin/src/feature/bedlevel/ubl/ubl.h
index 809e3827fe..cd13e081d9 100644
--- a/Marlin/src/feature/bedlevel/ubl/ubl.h
+++ b/Marlin/src/feature/bedlevel/ubl/ubl.h
@@ -26,6 +26,7 @@
 #include "../bedlevel.h"
 #include "../../../module/planner.h"
 #include "../../../module/motion.h"
+#include "../../../lcd/ultralcd.h"
 #include "../../../Marlin.h"
 
 #define UBL_VERSION "1.01"
@@ -49,12 +50,6 @@ enum MeshPointType : char { INVALID, REAL, SET_IN_BITMAP };
 
 // External references
 
-extern uint8_t ubl_cnt;
-
-#if ENABLED(ULTRA_LCD)
-  void lcd_quick_feedback(const bool clear_buttons);
-#endif
-
 #define MESH_X_DIST (float(MESH_MAX_X - (MESH_MIN_X)) / float(GRID_MAX_POINTS_X - 1))
 #define MESH_Y_DIST (float(MESH_MAX_Y - (MESH_MIN_Y)) / float(GRID_MAX_POINTS_Y - 1))
 
@@ -88,12 +83,15 @@ class unified_bed_leveling {
     static void probe_entire_mesh(const float &rx, const float &ry, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) _O0;
     static void tilt_mesh_based_on_3pts(const float &z1, const float &z2, const float &z3);
     static void tilt_mesh_based_on_probed_grid(const bool do_ubl_mesh_map);
-    static void g29_what_command();
-    static void g29_eeprom_dump();
-    static void g29_compare_current_mesh_to_stored_mesh();
     static bool smart_fill_one(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir);
     static void smart_fill_mesh();
 
+    #if ENABLED(UBL_DEVEL_DEBUGGING)
+      static void g29_what_command();
+      static void g29_eeprom_dump();
+      static void g29_compare_current_mesh_to_stored_mesh();
+    #endif
+
   public:
 
     static void echo_name(
diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
index 71c0097f78..80016c2f46 100644
--- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
+++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
@@ -53,7 +53,6 @@
   extern float destination[XYZE], current_position[XYZE];
 
   #if HAS_LCD_MENU
-    void lcd_return_to_status();
     void _lcd_ubl_output_map_lcd();
   #endif
 
@@ -345,9 +344,13 @@
       }
       SERIAL_PROTOCOLLNPGM("Loading test_pattern values.\n");
       switch (test_pattern) {
-        case -1:
-          g29_eeprom_dump();
-          break;
+
+        #if ENABLED(UBL_DEVEL_DEBUGGING)
+          case -1:
+            g29_eeprom_dump();
+            break;
+        #endif
+
         case 0:
           for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++) {   // Create a bowl shape - similar to
             for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++) { // a poorly calibrated Delta.
@@ -357,12 +360,14 @@
             }
           }
           break;
+
         case 1:
           for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++) {  // Create a diagonal line several Mesh cells thick that is raised
             z_values[x][x] += 9.999f;
             z_values[x][x + (x < GRID_MAX_POINTS_Y - 1) ? 1 : -1] += 9.999f; // We want the altered line several mesh points thick
           }
           break;
+
         case 2:
           // Allow the user to specify the height because 10mm is a little extreme in some cases.
           for (uint8_t x = (GRID_MAX_POINTS_X) / 3; x < 2 * (GRID_MAX_POINTS_X) / 3; x++)   // Create a rectangular raised area in
@@ -554,19 +559,24 @@
       }
     }
 
-    //
-    // Much of the 'What?' command can be eliminated. But until we are fully debugged, it is
-    // good to have the extra information. Soon... we prune this to just a few items
-    //
-    if (parser.seen('W')) g29_what_command();
+    #if ENABLED(UBL_DEVEL_DEBUGGING)
 
-    //
-    // When we are fully debugged, this may go away. But there are some valid
-    // use cases for the users. So we can wait and see what to do with it.
-    //
+      //
+      // Much of the 'What?' command can be eliminated. But until we are fully debugged, it is
+      // good to have the extra information. Soon... we prune this to just a few items
+      //
+      if (parser.seen('W')) g29_what_command();
+
+      //
+      // When we are fully debugged, this may go away. But there are some valid
+      // use cases for the users. So we can wait and see what to do with it.
+      //
+
+      if (parser.seen('K')) // Kompare Current Mesh Data to Specified Stored Mesh
+        g29_compare_current_mesh_to_stored_mesh();
+
+    #endif // UBL_DEVEL_DEBUGGING
 
-    if (parser.seen('K')) // Kompare Current Mesh Data to Specified Stored Mesh
-      g29_compare_current_mesh_to_stored_mesh();
 
     //
     // Load a Mesh from the EEPROM
@@ -629,10 +639,10 @@
     LEAVE:
 
     #if HAS_LCD_MENU
-      lcd_reset_alert_level();
-      lcd_quick_feedback();
-      lcd_reset_status();
-      lcd_external_control = false;
+      ui.reset_alert_level();
+      ui.quick_feedback();
+      ui.reset_status();
+      ui.release();
     #endif
 
     return;
@@ -683,30 +693,6 @@
           z_values[x][y] += g29_constant;
   }
 
-  #if HAS_LCD_MENU
-
-    typedef void (*clickFunc_t)();
-
-    bool click_and_hold(const clickFunc_t func=NULL) {
-      if (is_lcd_clicked()) {
-        lcd_quick_feedback(false);                // Preserve button state for click-and-hold
-        const millis_t nxt = millis() + 1500UL;
-        while (is_lcd_clicked()) {                // Loop while the encoder is pressed. Uses hardware flag!
-          idle();                                 // idle, of course
-          if (ELAPSED(millis(), nxt)) {           // After 1.5 seconds
-            lcd_quick_feedback();
-            if (func) (*func)();
-            wait_for_release();
-            return true;
-          }
-        }
-      }
-      safe_delay(15);
-      return false;
-    }
-
-  #endif // HAS_LCD_MENU
-
   #if HAS_BED_PROBE
     /**
      * Probe all invalidated locations of the mesh that can be reached by the probe.
@@ -716,10 +702,10 @@
       mesh_index_pair location;
 
       #if HAS_LCD_MENU
-        lcd_external_control = true;
+        ui.capture();
       #endif
 
-      save_ubl_active_state_and_disable();   // No bed level correction so only raw data is obtained
+      save_ubl_active_state_and_disable();  // No bed level correction so only raw data is obtained
       DEPLOY_PROBE();
 
       uint16_t count = GRID_MAX_POINTS;
@@ -728,13 +714,13 @@
         if (do_ubl_mesh_map) display_map(g29_map_type);
 
         #if HAS_LCD_MENU
-          if (is_lcd_clicked()) {
-            lcd_quick_feedback(false); // Preserve button state for click-and-hold
+          if (ui.button_pressed()) {
+            ui.quick_feedback(false); // Preserve button state for click-and-hold
             SERIAL_PROTOCOLLNPGM("\nMesh only partially populated.\n");
             STOW_PROBE();
-            wait_for_release();
-            lcd_quick_feedback();
-            lcd_external_control = false;
+            ui.wait_for_release();
+            ui.quick_feedback();
+            ui.release();
             restore_ubl_active_state_and_leave();
             return;
           }
@@ -769,14 +755,33 @@
       );
     }
 
-
   #endif // HAS_BED_PROBE
 
   #if HAS_LCD_MENU
 
+    typedef void (*clickFunc_t)();
+
+    bool click_and_hold(const clickFunc_t func=NULL) {
+      if (ui.button_pressed()) {
+        ui.quick_feedback(false);                // Preserve button state for click-and-hold
+        const millis_t nxt = millis() + 1500UL;
+        while (ui.button_pressed()) {                // Loop while the encoder is pressed. Uses hardware flag!
+          idle();                                 // idle, of course
+          if (ELAPSED(millis(), nxt)) {           // After 1.5 seconds
+            ui.quick_feedback();
+            if (func) (*func)();
+            ui.wait_for_release();
+            return true;
+          }
+        }
+      }
+      safe_delay(15);
+      return false;
+    }
+
     void unified_bed_leveling::move_z_with_encoder(const float &multiplier) {
-      wait_for_release();
-      while (!is_lcd_clicked()) {
+      ui.wait_for_release();
+      while (!ui.button_pressed()) {
         idle();
         gcode.reset_stepper_timeout(); // Keep steppers powered
         if (encoder_diff) {
@@ -796,7 +801,7 @@
     static void echo_and_take_a_measurement() { SERIAL_PROTOCOLLNPGM(" and take a measurement."); }
 
     float unified_bed_leveling::measure_business_card_thickness(float in_height) {
-      lcd_external_control = true;
+      ui.capture();
       save_ubl_active_state_and_disable();   // Disable bed level correction for probing
 
       do_blocking_move_to(0.5f * (MESH_MAX_X - (MESH_MIN_X)), 0.5f * (MESH_MAX_Y - (MESH_MIN_Y)), in_height);
@@ -805,7 +810,7 @@
 
       SERIAL_PROTOCOLPGM("Place shim under nozzle");
       LCD_MESSAGEPGM(MSG_UBL_BC_INSERT);
-      lcd_return_to_status();
+      ui.return_to_status();
       echo_and_take_a_measurement();
 
       const float z1 = measure_point_with_encoder();
@@ -828,7 +833,7 @@
         SERIAL_PROTOCOLLNPGM("mm thick.");
       }
 
-      lcd_external_control = false;
+      ui.release();
 
       restore_ubl_active_state_and_leave();
 
@@ -838,20 +843,20 @@
     void abort_manual_probe_remaining_mesh() {
       SERIAL_PROTOCOLLNPGM("\nMesh only partially populated.");
       do_blocking_move_to_z(Z_CLEARANCE_DEPLOY_PROBE);
-      lcd_external_control = false;
+      ui.release();
       KEEPALIVE_STATE(IN_HANDLER);
-      lcd_quick_feedback();
+      ui.quick_feedback();
       ubl.restore_ubl_active_state_and_leave();
     }
 
     void unified_bed_leveling::manually_probe_remaining_mesh(const float &rx, const float &ry, const float &z_clearance, const float &thick, const bool do_ubl_mesh_map) {
 
-      lcd_external_control = true;
+      ui.capture();
 
-      save_ubl_active_state_and_disable();   // we don't do bed level correction because we want the raw data when we probe
+      save_ubl_active_state_and_disable();  // No bed level correction so only raw data is obtained
       do_blocking_move_to(current_position[X_AXIS], current_position[Y_AXIS], z_clearance);
 
-      lcd_return_to_status();
+      ui.return_to_status();
 
       mesh_index_pair location;
       do {
@@ -870,7 +875,7 @@
         do_blocking_move_to_z(z_clearance);
 
         KEEPALIVE_STATE(PAUSED_FOR_USER);
-        lcd_external_control = true;
+        ui.capture();
 
         if (do_ubl_mesh_map) display_map(g29_map_type);  // show user where we're probing
 
@@ -884,7 +889,7 @@
         if (click_and_hold()) {
           SERIAL_PROTOCOLLNPGM("\nMesh only partially populated.");
           do_blocking_move_to_z(Z_CLEARANCE_DEPLOY_PROBE);
-          lcd_external_control = false;
+          ui.release();
           KEEPALIVE_STATE(IN_HANDLER);
           restore_ubl_active_state_and_leave();
           return;
@@ -905,12 +910,121 @@
       KEEPALIVE_STATE(IN_HANDLER);
       do_blocking_move_to(rx, ry, Z_CLEARANCE_DEPLOY_PROBE);
     }
-  #endif // HAS_LCD_MENU
 
-  inline void set_message_with_feedback(PGM_P const msg_P) {
-    lcd_setstatusPGM(msg_P);
-    lcd_quick_feedback();
-  }
+    inline void set_message_with_feedback(PGM_P const msg_P) {
+      ui.setstatusPGM(msg_P);
+      ui.quick_feedback();
+    }
+
+    void abort_fine_tune() {
+      ui.return_to_status();
+      do_blocking_move_to_z(Z_CLEARANCE_BETWEEN_PROBES);
+      set_message_with_feedback(PSTR(MSG_EDITING_STOPPED));
+    }
+
+    void unified_bed_leveling::fine_tune_mesh(const float &rx, const float &ry, const bool do_ubl_mesh_map) {
+      if (!parser.seen('R'))    // fine_tune_mesh() is special. If no repetition count flag is specified
+        g29_repetition_cnt = 1;   // do exactly one mesh location. Otherwise use what the parser decided.
+
+      #if ENABLED(UBL_MESH_EDIT_MOVES_Z)
+        const float h_offset = parser.seenval('H') ? parser.value_linear_units() : 0;
+        if (!WITHIN(h_offset, 0, 10)) {
+          SERIAL_PROTOCOLLNPGM("Offset out of bounds. (0 to 10mm)\n");
+          return;
+        }
+      #endif
+
+      mesh_index_pair location;
+
+      if (!position_is_reachable(rx, ry)) {
+        SERIAL_PROTOCOLLNPGM("(X,Y) outside printable radius.");
+        return;
+      }
+
+      save_ubl_active_state_and_disable();
+
+      LCD_MESSAGEPGM(MSG_UBL_FINE_TUNE_MESH);
+      ui.capture();                                                 // Take over control of the LCD encoder
+
+      do_blocking_move_to(rx, ry, Z_CLEARANCE_BETWEEN_PROBES);      // Move to the given XY with probe clearance
+
+      #if ENABLED(UBL_MESH_EDIT_MOVES_Z)
+        do_blocking_move_to_z(h_offset);                            // Move Z to the given 'H' offset
+      #endif
+
+      uint16_t not_done[16];
+      memset(not_done, 0xFF, sizeof(not_done));
+      do {
+        location = find_closest_mesh_point_of_type(SET_IN_BITMAP, rx, ry, USE_NOZZLE_AS_REFERENCE, not_done);
+
+        if (location.x_index < 0) break;                            // Stop when there are no more reachable points
+
+        bitmap_clear(not_done, location.x_index, location.y_index); // Mark this location as 'adjusted' so a new
+                                                                    // location is used on the next loop
+
+        const float rawx = mesh_index_to_xpos(location.x_index),
+                    rawy = mesh_index_to_ypos(location.y_index);
+
+        if (!position_is_reachable(rawx, rawy)) break;              // SHOULD NOT OCCUR because find_closest_mesh_point_of_type will only return reachable
+
+        do_blocking_move_to(rawx, rawy, Z_CLEARANCE_BETWEEN_PROBES); // Move the nozzle to the edit point with probe clearance
+
+        #if ENABLED(UBL_MESH_EDIT_MOVES_Z)
+          do_blocking_move_to_z(h_offset);                          // Move Z to the given 'H' offset before editing
+        #endif
+
+        KEEPALIVE_STATE(PAUSED_FOR_USER);
+
+        if (do_ubl_mesh_map) display_map(g29_map_type);             // Display the current point
+
+        ui.refresh();
+
+        float new_z = z_values[location.x_index][location.y_index];
+        if (isnan(new_z)) new_z = 0;                                // Invalid points begin at 0
+        new_z = FLOOR(new_z * 1000) * 0.001f;                       // Chop off digits after the 1000ths place
+
+        lcd_mesh_edit_setup(new_z);
+
+        do {
+          new_z = lcd_mesh_edit();
+          #if ENABLED(UBL_MESH_EDIT_MOVES_Z)
+            do_blocking_move_to_z(h_offset + new_z);                // Move the nozzle as the point is edited
+          #endif
+          idle();
+          SERIAL_FLUSH();                                           // Prevent host M105 buffer overrun.
+        } while (!ui.button_pressed());
+
+        if (!lcd_map_control) ui.return_to_status();               // Just editing a single point? Return to status
+
+        if (click_and_hold(abort_fine_tune)) goto FINE_TUNE_EXIT;   // If the click is held down, abort editing
+
+        z_values[location.x_index][location.y_index] = new_z;       // Save the updated Z value
+
+        safe_delay(20);                                             // No switch noise
+        ui.refresh();
+
+      } while (location.x_index >= 0 && --g29_repetition_cnt > 0);
+
+      FINE_TUNE_EXIT:
+
+      ui.release();
+      KEEPALIVE_STATE(IN_HANDLER);
+
+      if (do_ubl_mesh_map) display_map(g29_map_type);
+      restore_ubl_active_state_and_leave();
+
+      do_blocking_move_to(rx, ry, Z_CLEARANCE_BETWEEN_PROBES);
+
+      LCD_MESSAGEPGM(MSG_UBL_DONE_EDITING_MESH);
+      SERIAL_ECHOLNPGM("Done Editing Mesh");
+
+      if (lcd_map_control)
+        ui.goto_screen(_lcd_ubl_output_map_lcd);
+      else
+        ui.return_to_status();
+    }
+
+  #endif // HAS_LCD_MENU
 
   bool unified_bed_leveling::g29_parameter_parsing() {
     bool err_flag = false;
@@ -1060,170 +1174,6 @@
     set_bed_leveling_enabled(ubl_state_at_invocation);
   }
 
-  /**
-   * Much of the 'What?' command can be eliminated. But until we are fully debugged, it is
-   * good to have the extra information. Soon... we prune this to just a few items
-   */
-  void unified_bed_leveling::g29_what_command() {
-    report_state();
-
-    if (storage_slot == -1)
-      SERIAL_PROTOCOLPGM("No Mesh Loaded.");
-    else {
-      SERIAL_PROTOCOLPAIR("Mesh ", storage_slot);
-      SERIAL_PROTOCOLPGM(" Loaded.");
-    }
-    SERIAL_EOL();
-    safe_delay(50);
-
-    SERIAL_PROTOCOLLNPAIR("UBL object count: ", (int)ubl_cnt);
-
-    #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
-      SERIAL_PROTOCOLPGM("planner.z_fade_height : ");
-      SERIAL_PROTOCOL_F(planner.z_fade_height, 4);
-      SERIAL_EOL();
-    #endif
-
-    adjust_mesh_to_mean(g29_c_flag, g29_constant);
-
-    #if HAS_BED_PROBE
-      SERIAL_PROTOCOLPGM("zprobe_zoffset: ");
-      SERIAL_PROTOCOL_F(zprobe_zoffset, 7);
-      SERIAL_EOL();
-    #endif
-
-    SERIAL_ECHOLNPAIR("MESH_MIN_X  " STRINGIFY(MESH_MIN_X) "=", MESH_MIN_X);
-    safe_delay(50);
-    SERIAL_ECHOLNPAIR("MESH_MIN_Y  " STRINGIFY(MESH_MIN_Y) "=", MESH_MIN_Y);
-    safe_delay(50);
-    SERIAL_ECHOLNPAIR("MESH_MAX_X  " STRINGIFY(MESH_MAX_X) "=", MESH_MAX_X);
-    safe_delay(50);
-    SERIAL_ECHOLNPAIR("MESH_MAX_Y  " STRINGIFY(MESH_MAX_Y) "=", MESH_MAX_Y);
-    safe_delay(50);
-    SERIAL_ECHOLNPAIR("GRID_MAX_POINTS_X  ", GRID_MAX_POINTS_X);
-    safe_delay(50);
-    SERIAL_ECHOLNPAIR("GRID_MAX_POINTS_Y  ", GRID_MAX_POINTS_Y);
-    safe_delay(50);
-    SERIAL_ECHOLNPAIR("MESH_X_DIST  ", MESH_X_DIST);
-    SERIAL_ECHOLNPAIR("MESH_Y_DIST  ", MESH_Y_DIST);
-    safe_delay(50);
-
-    SERIAL_PROTOCOLPGM("X-Axis Mesh Points at: ");
-    for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) {
-      SERIAL_PROTOCOL_F(LOGICAL_X_POSITION(mesh_index_to_xpos(i)), 3);
-      SERIAL_PROTOCOLPGM("  ");
-      safe_delay(25);
-    }
-    SERIAL_EOL();
-
-    SERIAL_PROTOCOLPGM("Y-Axis Mesh Points at: ");
-    for (uint8_t i = 0; i < GRID_MAX_POINTS_Y; i++) {
-      SERIAL_PROTOCOL_F(LOGICAL_Y_POSITION(mesh_index_to_ypos(i)), 3);
-      SERIAL_PROTOCOLPGM("  ");
-      safe_delay(25);
-    }
-    SERIAL_EOL();
-
-    #if HAS_KILL
-      SERIAL_PROTOCOLPAIR("Kill pin on :", KILL_PIN);
-      SERIAL_PROTOCOLLNPAIR("  state:", READ(KILL_PIN));
-    #endif
-    SERIAL_EOL();
-    safe_delay(50);
-
-    #if ENABLED(UBL_DEVEL_DEBUGGING)
-      SERIAL_PROTOCOLLNPAIR("ubl_state_at_invocation :", ubl_state_at_invocation);
-      SERIAL_EOL();
-      SERIAL_PROTOCOLLNPAIR("ubl_state_recursion_chk :", ubl_state_recursion_chk);
-      SERIAL_EOL();
-      safe_delay(50);
-
-      SERIAL_PROTOCOLPAIR("Meshes go from ", hex_address((void*)settings.meshes_start_index()));
-      SERIAL_PROTOCOLLNPAIR(" to ", hex_address((void*)settings.meshes_end_index()));
-      safe_delay(50);
-
-      SERIAL_PROTOCOLLNPAIR("sizeof(ubl) :  ", (int)sizeof(ubl));
-      SERIAL_EOL();
-      SERIAL_PROTOCOLLNPAIR("z_value[][] size: ", (int)sizeof(z_values));
-      SERIAL_EOL();
-      safe_delay(25);
-
-      SERIAL_PROTOCOLLNPAIR("EEPROM free for UBL: ", hex_address((void*)(settings.meshes_end_index() - settings.meshes_start_index())));
-      safe_delay(50);
-
-      SERIAL_PROTOCOLPAIR("EEPROM can hold ", settings.calc_num_meshes());
-      SERIAL_PROTOCOLLNPGM(" meshes.\n");
-      safe_delay(25);
-    #endif // UBL_DEVEL_DEBUGGING
-
-    if (!sanity_check()) {
-      echo_name();
-      SERIAL_PROTOCOLLNPGM(" sanity checks passed.");
-    }
-  }
-
-  /**
-   * When we are fully debugged, the EEPROM dump command will get deleted also. But
-   * right now, it is good to have the extra information. Soon... we prune this.
-   */
-  void unified_bed_leveling::g29_eeprom_dump() {
-    uint8_t cccc;
-
-    SERIAL_ECHO_START();
-    SERIAL_ECHOLNPGM("EEPROM Dump:");
-    persistentStore.access_start();
-    for (uint16_t i = 0; i < persistentStore.capacity(); i += 16) {
-      if (!(i & 0x3)) idle();
-      print_hex_word(i);
-      SERIAL_ECHOPGM(": ");
-      for (uint16_t j = 0; j < 16; j++) {
-        persistentStore.read_data(i + j, &cccc, sizeof(uint8_t));
-        print_hex_byte(cccc);
-        SERIAL_ECHO(' ');
-      }
-      SERIAL_EOL();
-    }
-    SERIAL_EOL();
-    persistentStore.access_finish();
-  }
-
-  /**
-   * When we are fully debugged, this may go away. But there are some valid
-   * use cases for the users. So we can wait and see what to do with it.
-   */
-  void unified_bed_leveling::g29_compare_current_mesh_to_stored_mesh() {
-    int16_t a = settings.calc_num_meshes();
-
-    if (!a) {
-      SERIAL_PROTOCOLLNPGM("?EEPROM storage not available.");
-      return;
-    }
-
-    if (!parser.has_value()) {
-      SERIAL_PROTOCOLLNPGM("?Storage slot # required.");
-      SERIAL_PROTOCOLLNPAIR("?Use 0 to ", a - 1);
-      return;
-    }
-
-    g29_storage_slot = parser.value_int();
-
-    if (!WITHIN(g29_storage_slot, 0, a - 1)) {
-      SERIAL_PROTOCOLLNPGM("?Invalid storage slot.");
-      SERIAL_PROTOCOLLNPAIR("?Use 0 to ", a - 1);
-      return;
-    }
-
-    float tmp_z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
-    settings.load_mesh(g29_storage_slot, &tmp_z_values);
-
-    SERIAL_PROTOCOLPAIR("Subtracting mesh in slot ", g29_storage_slot);
-    SERIAL_PROTOCOLLNPGM(" from current mesh.");
-
-    for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++)
-      for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++)
-        z_values[x][y] -= tmp_z_values[x][y];
-  }
-
   mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() {
 
     bool found_a_NAN  = false, found_a_real = false;
@@ -1338,118 +1288,6 @@
     return out_mesh;
   }
 
-  #if HAS_LCD_MENU
-
-    void abort_fine_tune() {
-      lcd_return_to_status();
-      do_blocking_move_to_z(Z_CLEARANCE_BETWEEN_PROBES);
-      set_message_with_feedback(PSTR(MSG_EDITING_STOPPED));
-    }
-
-    void unified_bed_leveling::fine_tune_mesh(const float &rx, const float &ry, const bool do_ubl_mesh_map) {
-      if (!parser.seen('R'))    // fine_tune_mesh() is special. If no repetition count flag is specified
-        g29_repetition_cnt = 1;   // do exactly one mesh location. Otherwise use what the parser decided.
-
-      #if ENABLED(UBL_MESH_EDIT_MOVES_Z)
-        const float h_offset = parser.seenval('H') ? parser.value_linear_units() : 0;
-        if (!WITHIN(h_offset, 0, 10)) {
-          SERIAL_PROTOCOLLNPGM("Offset out of bounds. (0 to 10mm)\n");
-          return;
-        }
-      #endif
-
-      mesh_index_pair location;
-
-      if (!position_is_reachable(rx, ry)) {
-        SERIAL_PROTOCOLLNPGM("(X,Y) outside printable radius.");
-        return;
-      }
-
-      save_ubl_active_state_and_disable();
-
-      LCD_MESSAGEPGM(MSG_UBL_FINE_TUNE_MESH);
-      lcd_external_control = true;                                  // Take over control of the LCD encoder
-
-      do_blocking_move_to(rx, ry, Z_CLEARANCE_BETWEEN_PROBES);      // Move to the given XY with probe clearance
-
-      #if ENABLED(UBL_MESH_EDIT_MOVES_Z)
-        do_blocking_move_to_z(h_offset);                            // Move Z to the given 'H' offset
-      #endif
-
-      uint16_t not_done[16];
-      memset(not_done, 0xFF, sizeof(not_done));
-      do {
-        location = find_closest_mesh_point_of_type(SET_IN_BITMAP, rx, ry, USE_NOZZLE_AS_REFERENCE, not_done);
-
-        if (location.x_index < 0) break;                            // Stop when there are no more reachable points
-
-        bitmap_clear(not_done, location.x_index, location.y_index); // Mark this location as 'adjusted' so a new
-                                                                    // location is used on the next loop
-
-        const float rawx = mesh_index_to_xpos(location.x_index),
-                    rawy = mesh_index_to_ypos(location.y_index);
-
-        if (!position_is_reachable(rawx, rawy)) break;              // SHOULD NOT OCCUR because find_closest_mesh_point_of_type will only return reachable
-
-        do_blocking_move_to(rawx, rawy, Z_CLEARANCE_BETWEEN_PROBES); // Move the nozzle to the edit point with probe clearance
-
-        #if ENABLED(UBL_MESH_EDIT_MOVES_Z)
-          do_blocking_move_to_z(h_offset);                          // Move Z to the given 'H' offset before editing
-        #endif
-
-        KEEPALIVE_STATE(PAUSED_FOR_USER);
-
-        if (do_ubl_mesh_map) display_map(g29_map_type);             // Display the current point
-
-        lcd_refresh();
-
-        float new_z = z_values[location.x_index][location.y_index];
-        if (isnan(new_z)) new_z = 0;                                // Invalid points begin at 0
-        new_z = FLOOR(new_z * 1000) * 0.001f;                       // Chop off digits after the 1000ths place
-
-        lcd_mesh_edit_setup(new_z);
-
-        do {
-          new_z = lcd_mesh_edit();
-          #if ENABLED(UBL_MESH_EDIT_MOVES_Z)
-            do_blocking_move_to_z(h_offset + new_z);                // Move the nozzle as the point is edited
-          #endif
-          idle();
-          SERIAL_FLUSH();                                           // Prevent host M105 buffer overrun.
-        } while (!is_lcd_clicked());
-
-        if (!lcd_map_control) lcd_return_to_status();               // Just editing a single point? Return to status
-
-        if (click_and_hold(abort_fine_tune)) goto FINE_TUNE_EXIT;   // If the click is held down, abort editing
-
-        z_values[location.x_index][location.y_index] = new_z;       // Save the updated Z value
-
-        safe_delay(20);                                             // No switch noise
-        lcd_refresh();
-
-      } while (location.x_index >= 0 && --g29_repetition_cnt > 0);
-
-      FINE_TUNE_EXIT:
-
-      lcd_external_control = false;
-      KEEPALIVE_STATE(IN_HANDLER);
-
-      if (do_ubl_mesh_map) display_map(g29_map_type);
-      restore_ubl_active_state_and_leave();
-
-      do_blocking_move_to(rx, ry, Z_CLEARANCE_BETWEEN_PROBES);
-
-      LCD_MESSAGEPGM(MSG_UBL_DONE_EDITING_MESH);
-      SERIAL_ECHOLNPGM("Done Editing Mesh");
-
-      if (lcd_map_control)
-        lcd_goto_screen(_lcd_ubl_output_map_lcd);
-      else
-        lcd_return_to_status();
-    }
-
-  #endif // HAS_LCD_MENU
-
   /**
    * 'Smart Fill': Scan from the outward edges of the mesh towards the center.
    * If an invalid location is found, use the next two points (if valid) to
@@ -1823,4 +1661,158 @@
     }
   #endif // UBL_G29_P31
 
+  #if ENABLED(UBL_DEVEL_DEBUGGING)
+    /**
+     * Much of the 'What?' command can be eliminated. But until we are fully debugged, it is
+     * good to have the extra information. Soon... we prune this to just a few items
+     */
+    void unified_bed_leveling::g29_what_command() {
+      report_state();
+
+      if (storage_slot == -1)
+        SERIAL_PROTOCOLPGM("No Mesh Loaded.");
+      else {
+        SERIAL_PROTOCOLPAIR("Mesh ", storage_slot);
+        SERIAL_PROTOCOLPGM(" Loaded.");
+      }
+      SERIAL_EOL();
+      safe_delay(50);
+
+      #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+        SERIAL_PROTOCOLPGM("planner.z_fade_height : ");
+        SERIAL_PROTOCOL_F(planner.z_fade_height, 4);
+        SERIAL_EOL();
+      #endif
+
+      adjust_mesh_to_mean(g29_c_flag, g29_constant);
+
+      #if HAS_BED_PROBE
+        SERIAL_PROTOCOLPGM("zprobe_zoffset: ");
+        SERIAL_PROTOCOL_F(zprobe_zoffset, 7);
+        SERIAL_EOL();
+      #endif
+
+      SERIAL_ECHOLNPAIR("MESH_MIN_X  " STRINGIFY(MESH_MIN_X) "=", MESH_MIN_X); safe_delay(50);
+      SERIAL_ECHOLNPAIR("MESH_MIN_Y  " STRINGIFY(MESH_MIN_Y) "=", MESH_MIN_Y); safe_delay(50);
+      SERIAL_ECHOLNPAIR("MESH_MAX_X  " STRINGIFY(MESH_MAX_X) "=", MESH_MAX_X); safe_delay(50);
+      SERIAL_ECHOLNPAIR("MESH_MAX_Y  " STRINGIFY(MESH_MAX_Y) "=", MESH_MAX_Y); safe_delay(50);
+      SERIAL_ECHOLNPAIR("GRID_MAX_POINTS_X  ", GRID_MAX_POINTS_X);             safe_delay(50);
+      SERIAL_ECHOLNPAIR("GRID_MAX_POINTS_Y  ", GRID_MAX_POINTS_Y);             safe_delay(50);
+      SERIAL_ECHOLNPAIR("MESH_X_DIST  ", MESH_X_DIST);
+      SERIAL_ECHOLNPAIR("MESH_Y_DIST  ", MESH_Y_DIST);                         safe_delay(50);
+
+      SERIAL_PROTOCOLPGM("X-Axis Mesh Points at: ");
+      for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) {
+        SERIAL_PROTOCOL_F(LOGICAL_X_POSITION(mesh_index_to_xpos(i)), 3);
+        SERIAL_PROTOCOLPGM("  ");
+        safe_delay(25);
+      }
+      SERIAL_EOL();
+
+      SERIAL_PROTOCOLPGM("Y-Axis Mesh Points at: ");
+      for (uint8_t i = 0; i < GRID_MAX_POINTS_Y; i++) {
+        SERIAL_PROTOCOL_F(LOGICAL_Y_POSITION(mesh_index_to_ypos(i)), 3);
+        SERIAL_PROTOCOLPGM("  ");
+        safe_delay(25);
+      }
+      SERIAL_EOL();
+
+      #if HAS_KILL
+        SERIAL_PROTOCOLPAIR("Kill pin on :", KILL_PIN);
+        SERIAL_PROTOCOLLNPAIR("  state:", READ(KILL_PIN));
+      #endif
+      SERIAL_EOL();
+      safe_delay(50);
+
+      #if ENABLED(UBL_DEVEL_DEBUGGING)
+        SERIAL_PROTOCOLLNPAIR("ubl_state_at_invocation :", ubl_state_at_invocation); SERIAL_EOL();
+        SERIAL_PROTOCOLLNPAIR("ubl_state_recursion_chk :", ubl_state_recursion_chk); SERIAL_EOL();
+        safe_delay(50);
+
+        SERIAL_PROTOCOLPAIR("Meshes go from ", hex_address((void*)settings.meshes_start_index()));
+        SERIAL_PROTOCOLLNPAIR(" to ", hex_address((void*)settings.meshes_end_index()));
+        safe_delay(50);
+
+        SERIAL_PROTOCOLLNPAIR("sizeof(ubl) :  ", (int)sizeof(ubl));         SERIAL_EOL();
+        SERIAL_PROTOCOLLNPAIR("z_value[][] size: ", (int)sizeof(z_values)); SERIAL_EOL();
+        safe_delay(25);
+
+        SERIAL_PROTOCOLLNPAIR("EEPROM free for UBL: ", hex_address((void*)(settings.meshes_end_index() - settings.meshes_start_index())));
+        safe_delay(50);
+
+        SERIAL_PROTOCOLPAIR("EEPROM can hold ", settings.calc_num_meshes());
+        SERIAL_PROTOCOLLNPGM(" meshes.\n");
+        safe_delay(25);
+      #endif // UBL_DEVEL_DEBUGGING
+
+      if (!sanity_check()) {
+        echo_name();
+        SERIAL_PROTOCOLLNPGM(" sanity checks passed.");
+      }
+    }
+
+    /**
+     * When we are fully debugged, the EEPROM dump command will get deleted also. But
+     * right now, it is good to have the extra information. Soon... we prune this.
+     */
+    void unified_bed_leveling::g29_eeprom_dump() {
+      uint8_t cccc;
+
+      SERIAL_ECHO_START();
+      SERIAL_ECHOLNPGM("EEPROM Dump:");
+      persistentStore.access_start();
+      for (uint16_t i = 0; i < persistentStore.capacity(); i += 16) {
+        if (!(i & 0x3)) idle();
+        print_hex_word(i);
+        SERIAL_ECHOPGM(": ");
+        for (uint16_t j = 0; j < 16; j++) {
+          persistentStore.read_data(i + j, &cccc, sizeof(uint8_t));
+          print_hex_byte(cccc);
+          SERIAL_ECHO(' ');
+        }
+        SERIAL_EOL();
+      }
+      SERIAL_EOL();
+      persistentStore.access_finish();
+    }
+
+    /**
+     * When we are fully debugged, this may go away. But there are some valid
+     * use cases for the users. So we can wait and see what to do with it.
+     */
+    void unified_bed_leveling::g29_compare_current_mesh_to_stored_mesh() {
+      int16_t a = settings.calc_num_meshes();
+
+      if (!a) {
+        SERIAL_PROTOCOLLNPGM("?EEPROM storage not available.");
+        return;
+      }
+
+      if (!parser.has_value()) {
+        SERIAL_PROTOCOLLNPGM("?Storage slot # required.");
+        SERIAL_PROTOCOLLNPAIR("?Use 0 to ", a - 1);
+        return;
+      }
+
+      g29_storage_slot = parser.value_int();
+
+      if (!WITHIN(g29_storage_slot, 0, a - 1)) {
+        SERIAL_PROTOCOLLNPGM("?Invalid storage slot.");
+        SERIAL_PROTOCOLLNPAIR("?Use 0 to ", a - 1);
+        return;
+      }
+
+      float tmp_z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
+      settings.load_mesh(g29_storage_slot, &tmp_z_values);
+
+      SERIAL_PROTOCOLPAIR("Subtracting mesh in slot ", g29_storage_slot);
+      SERIAL_PROTOCOLLNPGM(" from current mesh.");
+
+      for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++)
+        for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++)
+          z_values[x][y] -= tmp_z_values[x][y];
+    }
+
+  #endif // UBL_DEVEL_DEBUGGING
+
 #endif // AUTO_BED_LEVELING_UBL
diff --git a/Marlin/src/feature/pause.cpp b/Marlin/src/feature/pause.cpp
index b26babf730..04d7682dfd 100644
--- a/Marlin/src/feature/pause.cpp
+++ b/Marlin/src/feature/pause.cpp
@@ -586,7 +586,7 @@ void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_le
   #endif
 
   #if ENABLED(ULTRA_LCD)
-    lcd_reset_status();
+    ui.reset_status();
   #endif
 }
 
diff --git a/Marlin/src/gcode/bedlevel/G26.cpp b/Marlin/src/gcode/bedlevel/G26.cpp
index 6deeadb326..7a44109262 100644
--- a/Marlin/src/gcode/bedlevel/G26.cpp
+++ b/Marlin/src/gcode/bedlevel/G26.cpp
@@ -163,12 +163,12 @@ int8_t g26_prime_flag;
    * If the LCD is clicked, cancel, wait for release, return true
    */
   bool user_canceled() {
-    if (!is_lcd_clicked()) return false; // Return if the button isn't pressed
-    lcd_setstatusPGM(PSTR("Mesh Validation Stopped."), 99);
+    if (!ui.button_pressed()) return false; // Return if the button isn't pressed
+    ui.setstatusPGM(PSTR("Mesh Validation Stopped."), 99);
     #if HAS_LCD_MENU
-      lcd_quick_feedback();
+      ui.quick_feedback();
     #endif
-    wait_for_release();
+    ui.wait_for_release();
     return true;
   }
 
@@ -414,10 +414,10 @@ inline bool turn_on_heaters() {
 
     if (g26_bed_temp > 25) {
       #if ENABLED(ULTRA_LCD)
-        lcd_setstatusPGM(PSTR("G26 Heating Bed."), 99);
-        lcd_quick_feedback();
+        ui.setstatusPGM(PSTR("G26 Heating Bed."), 99);
+        ui.quick_feedback();
         #if HAS_LCD_MENU
-          lcd_external_control = true;
+          ui.capture();
         #endif
       #endif
       thermalManager.setTargetBed(g26_bed_temp);
@@ -435,8 +435,8 @@ inline bool turn_on_heaters() {
 
   // Start heating the active nozzle
   #if ENABLED(ULTRA_LCD)
-    lcd_setstatusPGM(PSTR("G26 Heating Nozzle."), 99);
-    lcd_quick_feedback();
+    ui.setstatusPGM(PSTR("G26 Heating Nozzle."), 99);
+    ui.quick_feedback();
   #endif
   thermalManager.setTargetHotend(g26_hotend_temp, active_extruder);
 
@@ -449,8 +449,8 @@ inline bool turn_on_heaters() {
   ) return G26_ERR;
 
   #if ENABLED(ULTRA_LCD)
-    lcd_reset_status();
-    lcd_quick_feedback();
+    ui.reset_status();
+    ui.quick_feedback();
   #endif
 
   return G26_OK;
@@ -468,16 +468,16 @@ inline bool prime_nozzle() {
 
     if (g26_prime_flag == -1) {  // The user wants to control how much filament gets purged
 
-      lcd_external_control = true;
-      lcd_setstatusPGM(PSTR("User-Controlled Prime"), 99);
-      lcd_chirp();
+      ui.capture();
+      ui.setstatusPGM(PSTR("User-Controlled Prime"), 99);
+      ui.chirp();
 
       set_destination_from_current();
 
       recover_filament(destination); // Make sure G26 doesn't think the filament is retracted().
 
-      while (!is_lcd_clicked()) {
-        lcd_chirp();
+      while (!ui.button_pressed()) {
+        ui.chirp();
         destination[E_AXIS] += 0.25;
         #if ENABLED(PREVENT_LENGTHY_EXTRUDE)
           Total_Prime += 0.25;
@@ -491,18 +491,18 @@ inline bool prime_nozzle() {
                                   // action to give the user a more responsive 'Stop'.
       }
 
-      wait_for_release();
+      ui.wait_for_release();
 
-      lcd_setstatusPGM(PSTR("Done Priming"), 99);
-      lcd_quick_feedback();
-      lcd_external_control = false;
+      ui.setstatusPGM(PSTR("Done Priming"), 99);
+      ui.quick_feedback();
+      ui.release();
     }
     else
   #endif
   {
     #if ENABLED(ULTRA_LCD)
-      lcd_setstatusPGM(PSTR("Fixed Length Prime."), 99);
-      lcd_quick_feedback();
+      ui.setstatusPGM(PSTR("Fixed Length Prime."), 99);
+      ui.quick_feedback();
     #endif
     set_destination_from_current();
     destination[E_AXIS] += g26_prime_length;
@@ -715,7 +715,7 @@ void GcodeSuite::G26() {
   move_to(destination, g26_ooze_amount);
 
   #if HAS_LCD_MENU
-    lcd_external_control = true;
+    ui.capture();
   #endif
 
   //debug_current_and_destination(PSTR("Starting G26 Mesh Validation Pattern."));
@@ -881,8 +881,7 @@ void GcodeSuite::G26() {
   } while (--g26_repeats && location.x_index >= 0 && location.y_index >= 0);
 
   LEAVE:
-  lcd_setstatusPGM(PSTR("Leaving G26"), -1);
-  wait_for_release();
+  ui.setstatusPGM(PSTR("Leaving G26"), -1);
 
   retract_filament(destination);
   destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES;
@@ -891,15 +890,15 @@ void GcodeSuite::G26() {
   move_to(destination, 0); // Raise the nozzle
   //debug_current_and_destination(PSTR("done doing Z-Raise."));
 
-  destination[X_AXIS] = g26_x_pos;                               // Move back to the starting position
+  destination[X_AXIS] = g26_x_pos;                            // Move back to the starting position
   destination[Y_AXIS] = g26_y_pos;
-  //destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES;            // Keep the nozzle where it is
+  //destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES;         // Keep the nozzle where it is
 
-  move_to(destination, 0); // Move back to the starting position
+  move_to(destination, 0);                                    // Move back to the starting position
   //debug_current_and_destination(PSTR("done doing X/Y move."));
 
   #if HAS_LCD_MENU
-    lcd_external_control = false;     // Give back control of the LCD Panel!
+    ui.release();                                             // Give back control of the LCD
   #endif
 
   if (!g26_keep_heaters_on) {
diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp
index 547ef15612..ecc2196923 100644
--- a/Marlin/src/gcode/bedlevel/abl/G29.cpp
+++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp
@@ -498,7 +498,7 @@ G29_TYPE GcodeSuite::G29() {
       set_bed_leveling_enabled(abl_should_enable);
       g29_in_progress = false;
       #if ENABLED(LCD_BED_LEVELING)
-        lcd_wait_for_move = false;
+        ui.wait_for_bl_move = false;
       #endif
     }
 
@@ -790,7 +790,7 @@ G29_TYPE GcodeSuite::G29() {
   #if ENABLED(PROBE_MANUALLY)
     g29_in_progress = false;
     #if ENABLED(LCD_BED_LEVELING)
-      lcd_wait_for_move = false;
+      ui.wait_for_bl_move = false;
     #endif
   #endif
 
diff --git a/Marlin/src/gcode/bedlevel/mbl/G29.cpp b/Marlin/src/gcode/bedlevel/mbl/G29.cpp
index 16a7393e76..5bcc9e069a 100644
--- a/Marlin/src/gcode/bedlevel/mbl/G29.cpp
+++ b/Marlin/src/gcode/bedlevel/mbl/G29.cpp
@@ -90,7 +90,7 @@ void GcodeSuite::G29() {
     case MeshStart:
       mbl.reset();
       mbl_probe_index = 0;
-      if (!lcd_wait_for_move) {
+      if (!ui.wait_for_bl_move) {
         enqueue_and_echo_commands_P(PSTR("G28\nG29 S2"));
         return;
       }
@@ -151,7 +151,7 @@ void GcodeSuite::G29() {
         #endif
 
         #if ENABLED(LCD_BED_LEVELING)
-          lcd_wait_for_move = false;
+          ui.wait_for_bl_move = false;
         #endif
       }
       break;
diff --git a/Marlin/src/gcode/calibrate/G28.cpp b/Marlin/src/gcode/calibrate/G28.cpp
index 8a40e2bd61..29a0e6552c 100644
--- a/Marlin/src/gcode/calibrate/G28.cpp
+++ b/Marlin/src/gcode/calibrate/G28.cpp
@@ -425,7 +425,7 @@ void GcodeSuite::G28(const bool always_home_all) {
     tool_change(old_tool_index, 0, NO_FETCH);
   #endif
 
-  lcd_refresh();
+  ui.refresh();
 
   report_current_position();
   #if ENABLED(NANODLP_Z_SYNC)
diff --git a/Marlin/src/gcode/calibrate/G33.cpp b/Marlin/src/gcode/calibrate/G33.cpp
index 3aba869824..bb515a6ea3 100644
--- a/Marlin/src/gcode/calibrate/G33.cpp
+++ b/Marlin/src/gcode/calibrate/G33.cpp
@@ -522,7 +522,7 @@ void GcodeSuite::G33() {
   if (verbose_level == 0) SERIAL_PROTOCOLPGM(" (DRY-RUN)");
   if (set_up) SERIAL_PROTOCOLPGM("  (SET-UP)");
   SERIAL_EOL();
-  lcd_setstatusPGM(checkingac);
+  ui.setstatusPGM(checkingac);
 
   print_calibration_settings(_endstop_results, _angle_results);
 
@@ -683,7 +683,7 @@ void GcodeSuite::G33() {
           sprintf_P(&mess[15], PSTR("0.%03i"), (int)LROUND(zero_std_dev_min * 1000.0));
         else
           sprintf_P(&mess[15], PSTR("%03i.x"), (int)LROUND(zero_std_dev_min));
-        lcd_setstatus(mess);
+        ui.setstatus(mess);
         print_calibration_settings(_endstop_results, _angle_results);
         serialprintPGM(save_message);
         SERIAL_EOL();
@@ -699,7 +699,7 @@ void GcodeSuite::G33() {
         SERIAL_PROTOCOLPGM("std dev:");
         SERIAL_PROTOCOL_F(zero_std_dev, 3);
         SERIAL_EOL();
-        lcd_setstatus(mess);
+        ui.setstatus(mess);
         if (verbose_level > 1)
           print_calibration_settings(_endstop_results, _angle_results);
       }
@@ -719,7 +719,7 @@ void GcodeSuite::G33() {
         sprintf_P(&mess[15], PSTR("0.%03i"), (int)LROUND(zero_std_dev * 1000.0));
       else
         sprintf_P(&mess[15], PSTR("%03i.x"), (int)LROUND(zero_std_dev));
-      lcd_setstatus(mess);
+      ui.setstatus(mess);
     }
     ac_home();
   }
diff --git a/Marlin/src/gcode/control/M17_M18_M84.cpp b/Marlin/src/gcode/control/M17_M18_M84.cpp
index e4b94d99b6..33a6eae5ff 100644
--- a/Marlin/src/gcode/control/M17_M18_M84.cpp
+++ b/Marlin/src/gcode/control/M17_M18_M84.cpp
@@ -63,7 +63,7 @@ void GcodeSuite::M18_M84() {
     #if HAS_LCD_MENU && ENABLED(AUTO_BED_LEVELING_UBL)
       if (ubl.lcd_map_control) {
         ubl.lcd_map_control = false;
-        set_defer_return_to_status(false);
+        ui.defer_status_screen(false);
       }
     #endif
   }
diff --git a/Marlin/src/gcode/control/M80_M81.cpp b/Marlin/src/gcode/control/M80_M81.cpp
index 573a44bfa3..3dbf15a794 100644
--- a/Marlin/src/gcode/control/M80_M81.cpp
+++ b/Marlin/src/gcode/control/M80_M81.cpp
@@ -83,7 +83,7 @@
     #endif
 
     #if HAS_LCD_MENU
-      lcd_reset_status();
+      ui.reset_status();
     #endif
   }
 
diff --git a/Marlin/src/gcode/control/M999.cpp b/Marlin/src/gcode/control/M999.cpp
index 12c2cfdc59..18a7287aed 100644
--- a/Marlin/src/gcode/control/M999.cpp
+++ b/Marlin/src/gcode/control/M999.cpp
@@ -38,7 +38,7 @@
  */
 void GcodeSuite::M999() {
   Running = true;
-  lcd_reset_alert_level();
+  ui.reset_alert_level();
 
   if (parser.boolval('S')) return;
 
diff --git a/Marlin/src/gcode/lcd/M0_M1.cpp b/Marlin/src/gcode/lcd/M0_M1.cpp
index 505fef340e..3357e99c12 100644
--- a/Marlin/src/gcode/lcd/M0_M1.cpp
+++ b/Marlin/src/gcode/lcd/M0_M1.cpp
@@ -62,11 +62,11 @@ void GcodeSuite::M0_M1() {
   #if HAS_LCD_MENU
 
     if (has_message)
-      lcd_setstatus(args, true);
+      ui.setstatus(args, true);
     else {
       LCD_MESSAGEPGM(MSG_USERWAIT);
       #if ENABLED(LCD_PROGRESS_BAR) && PROGRESS_MSG_EXPIRE > 0
-        dontExpireStatus();
+        ui.reset_progress_bar_timeout();
       #endif
     }
 
@@ -94,7 +94,7 @@ void GcodeSuite::M0_M1() {
   #endif
 
   #if HAS_LCD_MENU
-    lcd_reset_status();
+    ui.reset_status();
   #endif
 
   wait_for_user = false;
diff --git a/Marlin/src/gcode/lcd/M117.cpp b/Marlin/src/gcode/lcd/M117.cpp
index 0f508d8eb9..c35a048a53 100644
--- a/Marlin/src/gcode/lcd/M117.cpp
+++ b/Marlin/src/gcode/lcd/M117.cpp
@@ -28,6 +28,6 @@
  */
 void GcodeSuite::M117() {
 
-  lcd_setstatus(parser.string_arg);
+  ui.setstatus(parser.string_arg);
 
 }
diff --git a/Marlin/src/gcode/lcd/M145.cpp b/Marlin/src/gcode/lcd/M145.cpp
index 9052a6493e..35fb525f48 100644
--- a/Marlin/src/gcode/lcd/M145.cpp
+++ b/Marlin/src/gcode/lcd/M145.cpp
@@ -37,7 +37,7 @@
  */
 void GcodeSuite::M145() {
   const uint8_t material = (uint8_t)parser.intval('S');
-  if (material >= COUNT(lcd_preheat_hotend_temp)) {
+  if (material >= COUNT(ui.preheat_hotend_temp)) {
     SERIAL_ERROR_START();
     SERIAL_ERRORLNPGM(MSG_ERR_MATERIAL_INDEX);
   }
@@ -45,16 +45,16 @@ void GcodeSuite::M145() {
     int v;
     if (parser.seenval('H')) {
       v = parser.value_int();
-      lcd_preheat_hotend_temp[material] = constrain(v, EXTRUDE_MINTEMP, HEATER_0_MAXTEMP - 15);
+      ui.preheat_hotend_temp[material] = constrain(v, EXTRUDE_MINTEMP, HEATER_0_MAXTEMP - 15);
     }
     if (parser.seenval('F')) {
       v = parser.value_int();
-      lcd_preheat_fan_speed[material] = (uint8_t)constrain(v, 0, 255);
+      ui.preheat_fan_speed[material] = (uint8_t)constrain(v, 0, 255);
     }
     #if TEMP_SENSOR_BED != 0
       if (parser.seenval('B')) {
         v = parser.value_int();
-        lcd_preheat_bed_temp[material] = constrain(v, BED_MINTEMP, BED_MAXTEMP - 15);
+        ui.preheat_bed_temp[material] = constrain(v, BED_MINTEMP, BED_MAXTEMP - 15);
       }
     #endif
   }
diff --git a/Marlin/src/gcode/lcd/M250.cpp b/Marlin/src/gcode/lcd/M250.cpp
index bede88126a..9444779f54 100644
--- a/Marlin/src/gcode/lcd/M250.cpp
+++ b/Marlin/src/gcode/lcd/M250.cpp
@@ -31,10 +31,8 @@
  * M250: Read and optionally set the LCD contrast
  */
 void GcodeSuite::M250() {
-  if (parser.seen('C')) set_lcd_contrast(parser.value_int());
-  SERIAL_PROTOCOLPGM("lcd contrast value: ");
-  SERIAL_PROTOCOL(lcd_contrast);
-  SERIAL_EOL();
+  if (parser.seen('C')) ui.set_contrast(parser.value_int());
+  SERIAL_PROTOCOLLNPAIR("LCD Contrast: ", ui.contrast);
 }
 
 #endif // HAS_LCD_CONTRAST
diff --git a/Marlin/src/gcode/lcd/M73.cpp b/Marlin/src/gcode/lcd/M73.cpp
index c721d5b363..78697aecf3 100644
--- a/Marlin/src/gcode/lcd/M73.cpp
+++ b/Marlin/src/gcode/lcd/M73.cpp
@@ -38,10 +38,8 @@
  *   This has no effect during an SD print job
  */
 void GcodeSuite::M73() {
-  if (!IS_SD_PRINTING() && parser.seen('P')) {
-    progress_bar_percent = parser.value_byte();
-    NOMORE(progress_bar_percent, 100);
-  }
+  if (parser.seen('P') && !IS_SD_PRINTING())
+    ui.set_progress(parser.value_byte());
 }
 
 #endif // ULTRA_LCD && LCD_SET_PROGRESS_MANUALLY
diff --git a/Marlin/src/gcode/motion/G4.cpp b/Marlin/src/gcode/motion/G4.cpp
index 7d53cb0304..6a0b6c2ddc 100644
--- a/Marlin/src/gcode/motion/G4.cpp
+++ b/Marlin/src/gcode/motion/G4.cpp
@@ -38,7 +38,7 @@ void GcodeSuite::G4() {
     SERIAL_ECHOLNPGM(MSG_Z_MOVE_COMP);
   #endif
 
-  if (!lcd_hasstatus()) LCD_MESSAGEPGM(MSG_DWELL);
+  if (!ui.hasstatus()) LCD_MESSAGEPGM(MSG_DWELL);
 
   dwell(dwell_ms);
 }
diff --git a/Marlin/src/gcode/stats/M31.cpp b/Marlin/src/gcode/stats/M31.cpp
index 958556e544..121a0cc806 100644
--- a/Marlin/src/gcode/stats/M31.cpp
+++ b/Marlin/src/gcode/stats/M31.cpp
@@ -40,7 +40,7 @@ void GcodeSuite::M31() {
   char buffer[21];
   duration_t elapsed = print_job_timer.duration();
   elapsed.toString(buffer);
-  lcd_setstatus(buffer);
+  ui.setstatus(buffer);
 
   SERIAL_ECHO_START_P(port);
   SERIAL_ECHOLNPAIR_P(port, "Print time: ", buffer);
diff --git a/Marlin/src/gcode/temperature/M104_M109.cpp b/Marlin/src/gcode/temperature/M104_M109.cpp
index 4868f2b4d3..c7499624c9 100644
--- a/Marlin/src/gcode/temperature/M104_M109.cpp
+++ b/Marlin/src/gcode/temperature/M104_M109.cpp
@@ -66,7 +66,7 @@ void GcodeSuite::M104() {
        */
       if (temp <= (EXTRUDE_MINTEMP) / 2) {
         print_job_timer.stop();
-        lcd_reset_status();
+        ui.reset_status();
       }
     #endif
   }
@@ -108,7 +108,7 @@ void GcodeSuite::M109() {
        */
       if (parser.value_celsius() <= (EXTRUDE_MINTEMP) / 2) {
         print_job_timer.stop();
-        lcd_reset_status();
+        ui.reset_status();
       }
       else
         print_job_timer.start();
diff --git a/Marlin/src/gcode/temperature/M140_M190.cpp b/Marlin/src/gcode/temperature/M140_M190.cpp
index 8a42eb85be..a70a6fdc85 100644
--- a/Marlin/src/gcode/temperature/M140_M190.cpp
+++ b/Marlin/src/gcode/temperature/M140_M190.cpp
@@ -64,7 +64,7 @@ void GcodeSuite::M190() {
   }
   else return;
 
-  lcd_setstatusPGM(thermalManager.isHeatingBed() ? PSTR(MSG_BED_HEATING) : PSTR(MSG_BED_COOLING));
+  ui.setstatusPGM(thermalManager.isHeatingBed() ? PSTR(MSG_BED_HEATING) : PSTR(MSG_BED_COOLING));
 
   thermalManager.wait_for_bed(no_wait_for_cooling);
 }
diff --git a/Marlin/src/inc/Conditionals_LCD.h b/Marlin/src/inc/Conditionals_LCD.h
index 0de18de582..b73370c787 100644
--- a/Marlin/src/inc/Conditionals_LCD.h
+++ b/Marlin/src/inc/Conditionals_LCD.h
@@ -296,47 +296,23 @@
   #define ULTIPANEL
 #endif
 
-#define HAS_GRAPHICAL_LCD ENABLED(DOGLCD)
-
-#if HAS_GRAPHICAL_LCD
-  #ifndef LCD_WIDTH
-    #ifdef LCD_WIDTH_OVERRIDE
-      #define LCD_WIDTH LCD_WIDTH_OVERRIDE
-    #else
-      #define LCD_WIDTH 22
-    #endif
-  #endif
-  #ifndef LCD_HEIGHT
-    #define LCD_HEIGHT 5
-  #endif
-#endif
-
 #if ENABLED(ULTIPANEL)
   #define NEWPANEL  // Disable this if you actually have no click-encoder panel
   #define ULTRA_LCD
-  #ifndef LCD_WIDTH
-    #define LCD_WIDTH 20
-  #endif
-  #ifndef LCD_HEIGHT
-    #define LCD_HEIGHT 4
-  #endif
-#elif ENABLED(ULTRA_LCD)  // no panel but just LCD
-  #ifndef LCD_WIDTH
-    #define LCD_WIDTH 16
-  #endif
-  #ifndef LCD_HEIGHT
-    #define LCD_HEIGHT 2
-  #endif
 #endif
 
 // Aliases for LCD features
 #define HAS_SPI_LCD          ENABLED(ULTRA_LCD)
-#define HAS_CHARACTER_LCD   (ENABLED(ULTRA_LCD) && DISABLED(DOGLCD))
+#define HAS_GRAPHICAL_LCD    ENABLED(DOGLCD)
+#define HAS_CHARACTER_LCD   (HAS_SPI_LCD && !HAS_GRAPHICAL_LCD)
 #define HAS_LCD_MENU        (ENABLED(ULTIPANEL) && DISABLED(NO_LCD_MENUS))
+#define HAS_DIGITAL_ENCODER  ENABLED(NEWPANEL)
 
 #if HAS_GRAPHICAL_LCD
-  /* Custom characters defined in font Marlin_symbols.fon which was merged to ISO10646-0-3.bdf */
+  //
+  // Custom characters from Marlin_symbols.fon which was merged into ISO10646-0-3.bdf
   // \x00 intentionally skipped to avoid problems in strings
+  //
   #define LCD_STR_REFRESH     "\x01"
   #define LCD_STR_FOLDER      "\x02"
   #define LCD_STR_ARROW_RIGHT "\x03"
@@ -354,33 +330,18 @@
   // Symbol characters
   #define LCD_STR_FILAM_DIA   "\xf8"
   #define LCD_STR_FILAM_MUL   "\xa4"
-#else
-  // Custom characters defined in the first 8 characters of the LCD
-  #define LCD_BEDTEMP_CHAR     0x00  // Print only as a char. This will have 'unexpected' results when used in a string!
-  #define LCD_DEGREE_CHAR      0x01
-  #define LCD_STR_THERMOMETER "\x02" // Still used with string concatenation
-  #define LCD_UPLEVEL_CHAR     0x03
-  #define LCD_STR_REFRESH     "\x04"
-  #define LCD_STR_FOLDER      "\x05"
-  #define LCD_FEEDRATE_CHAR    0x06
-  #define LCD_CLOCK_CHAR       0x07
-  #define LCD_STR_ARROW_RIGHT ">"  /* from the default character set */
-#endif
-
-/**
- * Default LCD contrast for dogm-like LCD displays
- */
-#if HAS_GRAPHICAL_LCD
 
-  #define HAS_LCD_CONTRAST ( \
-      ENABLED(MAKRPANEL) \
-   || ENABLED(CARTESIO_UI) \
-   || ENABLED(VIKI2) \
-   || ENABLED(AZSMZ_12864) \
-   || ENABLED(miniVIKI) \
-   || ENABLED(ELB_FULL_GRAPHIC_CONTROLLER) \
+  /**
+   * Default LCD contrast for dogm-like LCD displays
+   */
+  #define HAS_LCD_CONTRAST (                \
+       ENABLED(MAKRPANEL)                   \
+    || ENABLED(CARTESIO_UI)                 \
+    || ENABLED(VIKI2)                       \
+    || ENABLED(AZSMZ_12864)                 \
+    || ENABLED(miniVIKI)                    \
+    || ENABLED(ELB_FULL_GRAPHIC_CONTROLLER) \
   )
-
   #if HAS_LCD_CONTRAST
     #ifndef LCD_CONTRAST_MIN
       #define LCD_CONTRAST_MIN 0
@@ -392,6 +353,20 @@
       #define DEFAULT_LCD_CONTRAST 32
     #endif
   #endif
+
+#else
+
+  // Custom characters defined in the first 8 characters of the LCD
+  #define LCD_BEDTEMP_CHAR     0x00  // Print only as a char. This will have 'unexpected' results when used in a string!
+  #define LCD_DEGREE_CHAR      0x01
+  #define LCD_STR_THERMOMETER "\x02" // Still used with string concatenation
+  #define LCD_UPLEVEL_CHAR     0x03
+  #define LCD_STR_REFRESH     "\x04"
+  #define LCD_STR_FOLDER      "\x05"
+  #define LCD_FEEDRATE_CHAR    0x06
+  #define LCD_CLOCK_CHAR       0x07
+  #define LCD_STR_ARROW_RIGHT ">"  /* from the default character set */
+
 #endif
 
 // Boot screens
diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h
index a20ab25a73..1c7a3cb0b5 100644
--- a/Marlin/src/inc/Conditionals_post.h
+++ b/Marlin/src/inc/Conditionals_post.h
@@ -1628,3 +1628,39 @@
 #else
   #define Z_STEPPER_COUNT 1
 #endif
+
+// Get LCD character width/height, which may be overridden by pins, configs, etc.
+#if HAS_GRAPHICAL_LCD
+  #ifndef LCD_WIDTH
+    #ifdef LCD_WIDTH_OVERRIDE
+      #define LCD_WIDTH LCD_WIDTH_OVERRIDE
+    #elif ENABLED(LIGHTWEIGHT_UI)
+      #define LCD_WIDTH 16
+    #else
+      #define LCD_WIDTH 22
+    #endif
+  #endif
+  #ifndef LCD_HEIGHT
+    #ifdef LCD_HEIGHT_OVERRIDE
+      #define LCD_HEIGHT LCD_HEIGHT_OVERRIDE
+    #elif ENABLED(LIGHTWEIGHT_UI)
+      #define LCD_HEIGHT 4
+    #else
+      #define LCD_HEIGHT 5
+    #endif
+  #endif
+#elif ENABLED(ULTIPANEL)
+  #ifndef LCD_WIDTH
+    #define LCD_WIDTH 20
+  #endif
+  #ifndef LCD_HEIGHT
+    #define LCD_HEIGHT 4
+  #endif
+#elif HAS_SPI_LCD
+  #ifndef LCD_WIDTH
+    #define LCD_WIDTH 16
+  #endif
+  #ifndef LCD_HEIGHT
+    #define LCD_HEIGHT 2
+  #endif
+#endif
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index 2fd385aad2..80e022b82c 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -491,6 +491,8 @@ static_assert(X_MAX_LENGTH >= X_BED_SIZE && Y_MAX_LENGTH >= Y_BED_SIZE,
     #error "LCD_PROGRESS_BAR does not apply to graphical displays."
   #elif ENABLED(FILAMENT_LCD_DISPLAY)
     #error "LCD_PROGRESS_BAR and FILAMENT_LCD_DISPLAY are not fully compatible. Comment out this line to use both."
+  #elif PROGRESS_MSG_EXPIRE < 0
+    #error "PROGRESS_MSG_EXPIRE must be greater than or equal to 0."
   #endif
 #elif ENABLED(LCD_SET_PROGRESS_MANUALLY) && !HAS_GRAPHICAL_LCD
   #error "LCD_SET_PROGRESS_MANUALLY requires LCD_PROGRESS_BAR or Graphical LCD."
diff --git a/Marlin/src/lcd/HD44780/ultralcd_common_HD44780.h b/Marlin/src/lcd/HD44780/ultralcd_common_HD44780.h
index 54ff9a5368..9829619f54 100644
--- a/Marlin/src/lcd/HD44780/ultralcd_common_HD44780.h
+++ b/Marlin/src/lcd/HD44780/ultralcd_common_HD44780.h
@@ -39,70 +39,6 @@
 // macro name. The mapping is independent of whether the button is directly connected or
 // via a shift/i2c register.
 
-#if HAS_LCD_MENU
-
-  extern volatile uint8_t buttons;
-
-  //
-  // Setup other button mappings of each panel
-  //
-  #if ENABLED(LCD_I2C_VIKI)
-    #define B_I2C_BTN_OFFSET 3 // (the first three bit positions reserved for EN_A, EN_B, EN_C)
-
-    // button and encoder bit positions within 'buttons'
-    #define B_LE (BUTTON_LEFT   << B_I2C_BTN_OFFSET)    // The remaining normalized buttons are all read via I2C
-    #define B_UP (BUTTON_UP     << B_I2C_BTN_OFFSET)
-    #define B_MI (BUTTON_SELECT << B_I2C_BTN_OFFSET)
-    #define B_DW (BUTTON_DOWN   << B_I2C_BTN_OFFSET)
-    #define B_RI (BUTTON_RIGHT  << B_I2C_BTN_OFFSET)
-
-    #undef LCD_CLICKED
-    #if BUTTON_EXISTS(ENC)
-      // the pause/stop/restart button is connected to BTN_ENC when used
-      #define B_ST (EN_C)                            // Map the pause/stop/resume button into its normalized functional name
-      #define LCD_CLICKED() (buttons & (B_MI|B_RI|B_ST)) // pause/stop button also acts as click until we implement proper pause/stop.
-    #else
-      #define LCD_CLICKED() (buttons & (B_MI|B_RI))
-    #endif
-
-    // I2C buttons take too long to read inside an interrupt context and so we read them during lcd_update
-    #define LCD_HAS_SLOW_BUTTONS
-
-  #elif ENABLED(LCD_I2C_PANELOLU2)
-
-    #if !BUTTON_EXISTS(ENC) // Use I2C if not directly connected to a pin
-
-      #define B_I2C_BTN_OFFSET 3 // (the first three bit positions reserved for EN_A, EN_B, EN_C)
-
-      #define B_MI (PANELOLU2_ENCODER_C << B_I2C_BTN_OFFSET) // requires LiquidTWI2 library v1.2.3 or later
-
-      #undef LCD_CLICKED
-      #define LCD_CLICKED() (buttons & B_MI)
-
-      // I2C buttons take too long to read inside an interrupt context and so we read them during lcd_update
-      #define LCD_HAS_SLOW_BUTTONS
-
-    #endif
-
-  #elif DISABLED(NEWPANEL) // old style ULTIPANEL
-    // Shift register bits correspond to buttons:
-    #define BL_LE 7   // Left
-    #define BL_UP 6   // Up
-    #define BL_MI 5   // Middle
-    #define BL_DW 4   // Down
-    #define BL_RI 3   // Right
-    #define BL_ST 2   // Red Button
-    #define B_LE (_BV(BL_LE))
-    #define B_UP (_BV(BL_UP))
-    #define B_MI (_BV(BL_MI))
-    #define B_DW (_BV(BL_DW))
-    #define B_RI (_BV(BL_RI))
-    #define B_ST (_BV(BL_ST))
-    #define LCD_CLICKED() (buttons & (B_MI|B_ST))
-  #endif
-
-#endif // HAS_LCD_MENU
-
 ////////////////////////////////////
 // Create LCD class instance and chipset-specific information
 #if ENABLED(LCD_I2C_TYPE_PCF8575)
@@ -122,7 +58,7 @@
   #define LCD_CLASS LiquidCrystal_I2C
 
 #elif ENABLED(LCD_I2C_TYPE_MCP23017)
-  // For the LED indicators (which may be mapped to different events in lcd_implementation_update_indicators())
+  // For the LED indicators (which may be mapped to different events in update_indicators())
   #define LCD_HAS_STATUS_INDICATORS
   #define LED_A 0x04 //100
   #define LED_B 0x02 //010
diff --git a/Marlin/src/lcd/HD44780/ultralcd_impl_HD44780.cpp b/Marlin/src/lcd/HD44780/ultralcd_impl_HD44780.cpp
index f235366d29..edd38fb867 100644
--- a/Marlin/src/lcd/HD44780/ultralcd_impl_HD44780.cpp
+++ b/Marlin/src/lcd/HD44780/ultralcd_impl_HD44780.cpp
@@ -86,10 +86,6 @@
 
 #endif
 
-#if ENABLED(LCD_HAS_STATUS_INDICATORS)
-  static void lcd_implementation_update_indicators();
-#endif
-
 static void createChar_P(const char c, const byte * const ptr) {
   byte temp[8];
   for (uint8_t i = 0; i < 8; i++)
@@ -101,7 +97,7 @@ static void createChar_P(const char c, const byte * const ptr) {
   #define LCD_STR_PROGRESS  "\x03\x04\x05"
 #endif
 
-void lcd_set_custom_characters(
+void MarlinUI::set_custom_characters(
   #if ENABLED(LCD_PROGRESS_BAR) || ENABLED(SHOW_BOOTSCREEN)
     const HD44780CharSet screen_charset/*=CHARSET_INFO*/
   #endif
@@ -319,7 +315,7 @@ void lcd_set_custom_characters(
 
 }
 
-void lcd_implementation_init() {
+void MarlinUI::init_lcd() {
 
   #if ENABLED(LCD_I2C_TYPE_PCF8575)
     lcd.begin(LCD_WIDTH, LCD_HEIGHT);
@@ -331,7 +327,7 @@ void lcd_implementation_init() {
   #elif ENABLED(LCD_I2C_TYPE_MCP23017)
     lcd.setMCPType(LTI_TYPE_MCP23017);
     lcd.begin(LCD_WIDTH, LCD_HEIGHT);
-    lcd_implementation_update_indicators();
+    update_indicators();
 
   #elif ENABLED(LCD_I2C_TYPE_MCP23008)
     lcd.setMCPType(LTI_TYPE_MCP23008);
@@ -345,12 +341,12 @@ void lcd_implementation_init() {
     lcd.begin(LCD_WIDTH, LCD_HEIGHT);
   #endif
 
-  LCD_SET_CHARSET(currentScreen == lcd_status_screen ? CHARSET_INFO : CHARSET_MENU);
+  LCD_SET_CHARSET(on_status_screen() ? CHARSET_INFO : CHARSET_MENU);
 
   lcd.clear();
 }
 
-void lcd_implementation_clear() { lcd.clear(); }
+void MarlinUI::clear_lcd() { lcd.clear(); }
 
 #if ENABLED(SHOW_BOOTSCREEN)
 
@@ -408,7 +404,7 @@ void lcd_implementation_clear() { lcd.clear(); }
     lcd_moveto(indent, 2); lcd_put_wchar('\x02'); lcd_put_u8str_P(PSTR( "------" ));  lcd_put_wchar('\x03');
   }
 
-  void lcd_bootscreen() {
+  void MarlinUI::show_bootscreen() {
     LCD_SET_CHARSET(CHARSET_BOOT);
     lcd.clear();
 
@@ -454,6 +450,9 @@ void lcd_implementation_clear() { lcd.clear(); }
         CENTER_OR_SCROLL(STRING_SPLASH_LINE1, _SPLASH_WAIT_1);
         #ifdef STRING_SPLASH_LINE2
           CENTER_OR_SCROLL(STRING_SPLASH_LINE2, 1500);
+          #ifdef STRING_SPLASH_LINE3
+            CENTER_OR_SCROLL(STRING_SPLASH_LINE3, 1500);
+          #endif
         #endif
       }
     #elif defined(STRING_SPLASH_LINE2)
@@ -484,9 +483,9 @@ void lcd_implementation_clear() { lcd.clear(); }
 
 #endif // SHOW_BOOTSCREEN
 
-void lcd_kill_screen() {
+void MarlinUI::draw_kill_screen() {
   lcd_moveto(0, 0);
-  lcd_put_u8str(lcd_status_message);
+  lcd_put_u8str(status_message);
   #if LCD_HEIGHT < 4
     lcd_moveto(0, 2);
   #else
@@ -572,13 +571,7 @@ FORCE_INLINE void _draw_bed_status(const bool blink) {
 #if HAS_PRINT_PROGRESS
 
   FORCE_INLINE void _draw_print_progress() {
-    const uint8_t percent = (
-      #if ENABLED(SDSUPPORT)
-        IS_SD_PRINTING() ? card.percentDone() : 0
-      #else
-        progress_bar_percent
-      #endif
-    );
+    const uint8_t progress = ui.get_progress();
     lcd_put_u8str_P(PSTR(
       #if ENABLED(SDSUPPORT)
         "SD"
@@ -586,8 +579,8 @@ FORCE_INLINE void _draw_bed_status(const bool blink) {
         "P:"
       #endif
     ));
-    if (percent)
-      lcd_put_u8str(itostr3(percent));
+    if (progress)
+      lcd_put_u8str(itostr3(progress));
     else
       lcd_put_u8str_P(PSTR("---"));
     lcd_put_wchar('%');
@@ -616,7 +609,7 @@ FORCE_INLINE void _draw_bed_status(const bool blink) {
 
 #endif // LCD_PROGRESS_BAR
 
-FORCE_INLINE void _draw_status_message(const bool blink) {
+void MarlinUI::draw_status_message(const bool blink) {
 
   lcd_moveto(0, LCD_HEIGHT - 1);
 
@@ -624,17 +617,15 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
 
     // Draw the progress bar if the message has shown long enough
     // or if there is no message set.
-    #if DISABLED(LCD_SET_PROGRESS_MANUALLY)
-      const uint8_t progress_bar_percent = card.percentDone();
-    #endif
-    if (progress_bar_percent > 2 && (ELAPSED(millis(), progress_bar_ms + PROGRESS_BAR_MSG_TIME) || !lcd_status_message[0]))
-      return lcd_draw_progress_bar(progress_bar_percent);
+    if (ELAPSED(millis(), progress_bar_ms + PROGRESS_BAR_MSG_TIME) || !has_status()) {
+      const uint8_t progress = get_progress();
+      if (progress > 2) return lcd_draw_progress_bar(progress);
+    }
 
   #elif ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
 
-    // Show Filament Diameter and Volumetric Multiplier %
-    // After allowing lcd_status_message to show for 5 seconds
-    if (ELAPSED(millis(), previous_lcd_status_ms + 5000UL)) {
+    // Alternate Status message and Filament display
+    if (ELAPSED(millis(), next_filament_display)) {
       lcd_put_u8str_P(PSTR("Dia "));
       lcd_put_u8str(ftostr12ns(filament_width_meas));
       lcd_put_u8str_P(PSTR(" V"));
@@ -654,13 +645,13 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
     static bool last_blink = false;
 
     // Get the UTF8 character count of the string
-    uint8_t slen = utf8_strlen(lcd_status_message);
+    uint8_t slen = utf8_strlen(status_message);
 
     // If the string fits into the LCD, just print it and do not scroll it
     if (slen <= LCD_WIDTH) {
 
       // The string isn't scrolling and may not fill the screen
-      lcd_put_u8str(lcd_status_message);
+      lcd_put_u8str(status_message);
 
       // Fill the rest with spaces
       while (slen < LCD_WIDTH) {
@@ -672,7 +663,7 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
       // String is larger than the available space in screen.
 
       // Get a pointer to the next valid UTF8 character
-      const char *stat = lcd_status_message + status_scroll_offset;
+      const char *stat = status_message + status_scroll_offset;
 
       // Get the string remaining length
       const uint8_t rlen = utf8_strlen(stat);
@@ -692,7 +683,7 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
         if (--chars) {                                    // Draw a second dot if there's space
           lcd_put_wchar('.');
           if (--chars)
-            lcd_put_u8str_max(lcd_status_message, chars); // Print a second copy of the message
+            lcd_put_u8str_max(status_message, chars); // Print a second copy of the message
         }
       }
       if (last_blink != blink) {
@@ -701,7 +692,7 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
         // Adjust by complete UTF8 characters
         if (status_scroll_offset < slen) {
           status_scroll_offset++;
-          while (!START_OF_UTF8_CHAR(lcd_status_message[status_scroll_offset]))
+          while (!START_OF_UTF8_CHAR(status_message[status_scroll_offset]))
             status_scroll_offset++;
         }
         else
@@ -712,10 +703,10 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
     UNUSED(blink);
 
     // Get the UTF8 character count of the string
-    uint8_t slen = utf8_strlen(lcd_status_message);
+    uint8_t slen = utf8_strlen(status_message);
 
     // Just print the string to the LCD
-    lcd_put_u8str_max(lcd_status_message, LCD_WIDTH);
+    lcd_put_u8str_max(status_message, LCD_WIDTH);
 
     // Fill the rest with spaces if there are missing spaces
     while (slen < LCD_WIDTH) {
@@ -725,34 +716,46 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
   #endif
 }
 
-#if LCD_INFO_SCREEN_STYLE == 0
-
-  /**
-   *  LCD_INFO_SCREEN_STYLE 0 : Classic Status Screen
-   *
-   *  16x2   |000/000 B000/000|
-   *         |0123456789012345|
-   *
-   *  16x4   |000/000 B000/000|
-   *         |SD---%  Z 000.00|
-   *         |F---%     T--:--|
-   *         |0123456789012345|
-   *
-   *  20x2   |T000/000° B000/000° |
-   *         |01234567890123456789|
-   *
-   *  20x4   |T000/000° B000/000° |
-   *         |X 000 Y 000 Z000.000|
-   *         |F---%  SD---% T--:--|
-   *         |01234567890123456789|
-   */
-
-  void lcd_impl_status_screen_0() {
-    const bool blink = lcd_blink();
+/**
+ *  LCD_INFO_SCREEN_STYLE 0 : Classic Status Screen
+ *
+ *  16x2   |000/000 B000/000|
+ *         |0123456789012345|
+ *
+ *  16x4   |000/000 B000/000|
+ *         |SD---%  Z 000.00|
+ *         |F---%     T--:--|
+ *         |0123456789012345|
+ *
+ *  20x2   |T000/000° B000/000° |
+ *         |01234567890123456789|
+ *
+ *  20x4   |T000/000° B000/000° |
+ *         |X 000 Y 000 Z000.000|
+ *         |F---%  SD---% T--:--|
+ *         |01234567890123456789|
+ *
+ *  LCD_INFO_SCREEN_STYLE 1 : Prusa-style Status Screen
+ *
+ *  |T000/000°  Z 000.00 |
+ *  |B000/000°  F---%    |
+ *  |SD---%     T--:--   |
+ *  |01234567890123456789|
+ *
+ *  |T000/000°  Z 000.00 |
+ *  |T000/000°  F---%    |
+ *  |B000/000°  SD---%   |
+ *  |01234567890123456789|
+ */
+
+void MarlinUI::draw_status_screen() {
 
-    // ========== Line 1 ==========
+  const bool blink = get_blink();
+  lcd_moveto(0, 0);
+
+  #if LCD_INFO_SCREEN_STYLE == 0
 
-    lcd_moveto(0, 0);
+    // ========== Line 1 ==========
 
     #if LCD_WIDTH < 20
 
@@ -885,39 +888,13 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
 
     #endif // LCD_HEIGHT > 3
 
-    // ========= Last Line ========
-
-    //
-    // Status Message (which may be a Progress Bar or Filament display)
-    //
-    _draw_status_message(blink);
-  }
-
-#elif LCD_INFO_SCREEN_STYLE == 1
-
-  /**
-   *  LCD_INFO_SCREEN_STYLE 1 : Prusa-style Status Screen
-   *
-   *  |T000/000°  Z 000.00 |
-   *  |B000/000°  F---%    |
-   *  |SD---%     T--:--   |
-   *  |01234567890123456789|
-   *
-   *  |T000/000°  Z 000.00 |
-   *  |T000/000°  F---%    |
-   *  |B000/000°  SD---%   |
-   *  |01234567890123456789|
-   */
-
-  void lcd_impl_status_screen_1() {
-    const bool blink = lcd_blink();
+  #elif LCD_INFO_SCREEN_STYLE == 1
 
     // ========== Line 1 ==========
 
     //
     // Hotend 0 Temperature
     //
-    lcd_moveto(0, 0);
     _draw_heater_status(0, LCD_STR_THERMOMETER[0], blink);
 
     //
@@ -977,30 +954,30 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
       lcd_put_u8str(buffer);
     #endif
 
-    // ========== Line 4 ==========
+  #endif // LCD_INFO_SCREEN_STYLE 1
 
-    //
-    // Status Message (which may be a Progress Bar or Filament display)
-    //
-    _draw_status_message(blink);
-  }
+  // ========= Last Line ========
 
-#endif
+  //
+  // Status Message (which may be a Progress Bar or Filament display)
+  //
+  draw_status_message(blink);
+}
 
 #if HAS_LCD_MENU
 
   #if ENABLED(ADVANCED_PAUSE_FEATURE)
 
-    void lcd_implementation_hotend_status(const uint8_t row, const uint8_t extruder) {
+    void MarlinUI::draw_hotend_status(const uint8_t row, const uint8_t extruder) {
       if (row < LCD_HEIGHT) {
         lcd_moveto(LCD_WIDTH - 9, row);
-        _draw_heater_status(extruder, LCD_STR_THERMOMETER[0], lcd_blink());
+        _draw_heater_status(extruder, LCD_STR_THERMOMETER[0], ui.get_blink());
       }
     }
 
   #endif // ADVANCED_PAUSE_FEATURE
 
-  void lcd_implementation_drawmenu_static(const uint8_t row, PGM_P pstr, const bool center/*=true*/, const bool invert/*=false*/, const char *valstr/*=NULL*/) {
+  void draw_menu_item_static(const uint8_t row, PGM_P pstr, const bool center/*=true*/, const bool invert/*=false*/, const char *valstr/*=NULL*/) {
     UNUSED(invert);
     int8_t n = LCD_WIDTH;
     lcd_moveto(0, row);
@@ -1013,35 +990,35 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
     for (; n > 0; --n) lcd_put_wchar(' ');
   }
 
-  void lcd_implementation_drawmenu_generic(const bool sel, const uint8_t row, PGM_P pstr, const char pre_char, const char post_char) {
+  void draw_menu_item_generic(const bool isSelected, const uint8_t row, PGM_P pstr, const char pre_char, const char post_char) {
     uint8_t n = LCD_WIDTH - 2;
     lcd_moveto(0, row);
-    lcd_put_wchar(sel ? pre_char : ' ');
+    lcd_put_wchar(isSelected ? pre_char : ' ');
     n -= lcd_put_u8str_max_P(pstr, n);
     while (n--) lcd_put_wchar(' ');
     lcd_put_wchar(post_char);
   }
 
-  void lcd_implementation_drawmenu_setting_edit_generic(const bool sel, const uint8_t row, PGM_P pstr, const char pre_char, const char* const data) {
+  void draw_menu_item_setting_edit_generic(const bool isSelected, const uint8_t row, PGM_P pstr, const char pre_char, const char* const data) {
     uint8_t n = LCD_WIDTH - 2 - utf8_strlen(data);
     lcd_moveto(0, row);
-    lcd_put_wchar(sel ? pre_char : ' ');
+    lcd_put_wchar(isSelected ? pre_char : ' ');
     n -= lcd_put_u8str_max_P(pstr, n);
     lcd_put_wchar(':');
     while (n--) lcd_put_wchar(' ');
     lcd_put_u8str(data);
   }
-  void lcd_implementation_drawmenu_setting_edit_generic_P(const bool sel, const uint8_t row, PGM_P pstr, const char pre_char, const char* const data) {
+  void draw_menu_item_setting_edit_generic_P(const bool isSelected, const uint8_t row, PGM_P pstr, const char pre_char, const char* const data) {
     uint8_t n = LCD_WIDTH - 2 - utf8_strlen_P(data);
     lcd_moveto(0, row);
-    lcd_put_wchar(sel ? pre_char : ' ');
+    lcd_put_wchar(isSelected ? pre_char : ' ');
     n -= lcd_put_u8str_max_P(pstr, n);
     lcd_put_wchar(':');
     while (n--) lcd_put_wchar(' ');
     lcd_put_u8str_P(data);
   }
 
-  void lcd_implementation_drawedit(PGM_P pstr, const char* const value/*=NULL*/) {
+  void draw_edit_screen(PGM_P const pstr, const char* const value/*=NULL*/) {
     lcd_moveto(1, 1);
     lcd_put_u8str_P(pstr);
     if (value != NULL) {
@@ -1056,27 +1033,29 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
 
   #if ENABLED(SDSUPPORT)
 
-    static void lcd_implementation_drawmenu_sd(const bool sel, const uint8_t row, PGM_P const pstr, CardReader &theCard, const uint8_t concat, const char post_char) {
+    void draw_sd_menu_item(const bool isSelected, const uint8_t row, PGM_P const pstr, CardReader &theCard, const bool isDir) {
+      const char post_char = isDir ? LCD_STR_FOLDER[0] : ' ',
+                 sel_char = isSelected ? '>' : ' ';
       UNUSED(pstr);
       lcd_moveto(0, row);
-      lcd_put_wchar(sel ? '>' : ' ');
+      lcd_put_wchar(sel_char);
 
-      uint8_t n = LCD_WIDTH - concat;
+      uint8_t n = LCD_WIDTH - 2;
       const char *outstr = theCard.longest_filename();
       if (theCard.longFilename[0]) {
         #if ENABLED(SCROLL_LONG_FILENAMES)
           static uint8_t filename_scroll_hash;
-          if (sel) {
+          if (isSelected) {
             uint8_t name_hash = row;
             for (uint8_t l = FILENAME_LENGTH; l--;)
               name_hash = ((name_hash << 1) | (name_hash >> 7)) ^ theCard.filename[l];  // rotate, xor
             if (filename_scroll_hash != name_hash) {                            // If the hash changed...
               filename_scroll_hash = name_hash;                                 // Save the new hash
-              filename_scroll_max = MAX(0, utf8_strlen(theCard.longFilename) - n);  // Update the scroll limit
-              filename_scroll_pos = 0;                                          // Reset scroll to the start
-              lcd_status_update_delay = 8;                                      // Don't scroll right away
+              ui.filename_scroll_max = MAX(0, utf8_strlen(theCard.longFilename) - n); // Update the scroll limit
+              ui.filename_scroll_pos = 0;                                       // Reset scroll to the start
+              ui.lcd_status_update_delay = 8;                                   // Don't scroll right away
             }
-            outstr += filename_scroll_pos;
+            outstr += ui.filename_scroll_pos;
           }
         #else
           theCard.longFilename[n] = '\0'; // cutoff at screen edge
@@ -1084,45 +1063,18 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
       }
 
       lcd_moveto(0, row);
-      lcd_put_wchar(sel ? '>' : ' ');
+      lcd_put_wchar(sel_char);
       n -= lcd_put_u8str_max(outstr, n);
 
-      while (n) { --n; lcd_put_wchar(' '); }
+      for (; n; --n) lcd_put_wchar(' ');
       lcd_put_wchar(post_char);
     }
 
-    void lcd_implementation_drawmenu_sdfile(const bool sel, const uint8_t row, PGM_P pstr, CardReader &theCard) {
-      lcd_implementation_drawmenu_sd(sel, row, pstr, theCard, 2, ' ');
-    }
-
-    void lcd_implementation_drawmenu_sddirectory(const bool sel, const uint8_t row, PGM_P pstr, CardReader &theCard) {
-      lcd_implementation_drawmenu_sd(sel, row, pstr, theCard, 2, LCD_STR_FOLDER[0]);
-    }
-
   #endif // SDSUPPORT
 
-  #if ENABLED(LCD_HAS_SLOW_BUTTONS)
-
-    extern millis_t next_button_update_ms;
-
-    static uint8_t lcd_implementation_read_slow_buttons() {
-      #if ENABLED(LCD_I2C_TYPE_MCP23017)
-        // Reading these buttons this is likely to be too slow to call inside interrupt context
-        // so they are called during normal lcd_update
-        uint8_t slow_bits = lcd.readButtons() << B_I2C_BTN_OFFSET;
-        #if ENABLED(LCD_I2C_VIKI)
-          if ((slow_bits & (B_MI | B_RI)) && PENDING(millis(), next_button_update_ms)) // LCD clicked
-            slow_bits &= ~(B_MI | B_RI); // Disable LCD clicked buttons if screen is updated
-        #endif // LCD_I2C_VIKI
-        return slow_bits;
-      #endif // LCD_I2C_TYPE_MCP23017
-    }
-
-  #endif // LCD_HAS_SLOW_BUTTONS
-
   #if ENABLED(LCD_HAS_STATUS_INDICATORS)
 
-    static void lcd_implementation_update_indicators() {
+    static void MarlinUI::update_indicators() {
       // Set the LEDS - referred to as backlights by the LiquidTWI2 library
       static uint8_t ledsprev = 0;
       uint8_t leds = 0;
@@ -1242,7 +1194,7 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
       lcd_put_wchar(c);
     }
 
-    void lcd_implementation_ubl_plot(const uint8_t x, const uint8_t inverted_y) {
+    void MarlinUI::ubl_plot(const uint8_t x, const uint8_t inverted_y) {
 
       #if LCD_WIDTH >= 20
         #define _LCD_W_POS 12
@@ -1292,7 +1244,7 @@ FORCE_INLINE void _draw_status_message(const bool blink) {
         lower_right.column = 0;
         lower_right.row    = 0;
 
-        lcd_implementation_clear();
+        clear_lcd();
 
         x_map_pixels = (HD44780_CHAR_WIDTH) * (MESH_MAP_COLS) - 2;          // Minus 2 because we are drawing a box around the map
         y_map_pixels = (HD44780_CHAR_HEIGHT) * (MESH_MAP_ROWS) - 2;
diff --git a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
index c49fa50a05..af209a07df 100644
--- a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
+++ b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
@@ -135,35 +135,29 @@ FORCE_INLINE void _draw_axis_value(const AxisEnum axis, const char *value, const
   }
 }
 
-FORCE_INLINE void lcd_implementation_status_message(const bool blink) {
-  #if ENABLED(STATUS_MESSAGE_SCROLLING)
-    static bool last_blink = false;
+void MarlinUI::draw_status_message(const bool blink) {
 
-    // Get the UTF8 character count of the string
-    uint8_t slen = utf8_strlen(lcd_status_message);
+  // Get the UTF8 character count of the string
+  uint8_t slen = utf8_strlen(status_message);
 
-    // If the string fits into the LCD, just print it and do not scroll it
-    if (slen <= LCD_WIDTH) {
+  #if ENABLED(STATUS_MESSAGE_SCROLLING)
 
-      // The string isn't scrolling and may not fill the screen
-      lcd_put_u8str(lcd_status_message);
+    static bool last_blink = false;
 
-      // Fill the rest with spaces
-      while (slen < LCD_WIDTH) {
-        lcd_put_wchar(' ');
-        ++slen;
-      }
+    if (slen <= LCD_WIDTH) {
+      // The string fits within the line. Print with no scrolling
+      lcd_put_u8str(status_message);
+      for (; slen < LCD_WIDTH; ++slen) lcd_put_wchar(' ');
     }
     else {
-      // String is larger than the available space in screen.
+      // String is longer than the available space
 
       // Get a pointer to the next valid UTF8 character
-      const char *stat = lcd_status_message + status_scroll_offset;
+      const char *stat = status_message + status_scroll_offset;
 
       // Get the string remaining length
       const uint8_t rlen = utf8_strlen(stat);
 
-      // If we have enough characters to display
       if (rlen >= LCD_WIDTH) {
         // The remaining string fills the screen - Print it
         lcd_put_u8str_max(stat, LCD_PIXEL_WIDTH);
@@ -178,7 +172,7 @@ FORCE_INLINE void lcd_implementation_status_message(const bool blink) {
           lcd_put_wchar('.');
           if (--chars) {
             // Print a second copy of the message
-            lcd_put_u8str_max(lcd_status_message, LCD_PIXEL_WIDTH - ((rlen+2) * MENU_FONT_WIDTH));
+            lcd_put_u8str_max(status_message, LCD_PIXEL_WIDTH - (rlen + 2) * (MENU_FONT_WIDTH));
           }
         }
       }
@@ -188,36 +182,33 @@ FORCE_INLINE void lcd_implementation_status_message(const bool blink) {
         // Adjust by complete UTF8 characters
         if (status_scroll_offset < slen) {
           status_scroll_offset++;
-          while (!START_OF_UTF8_CHAR(lcd_status_message[status_scroll_offset]))
+          while (!START_OF_UTF8_CHAR(status_message[status_scroll_offset]))
             status_scroll_offset++;
         }
         else
           status_scroll_offset = 0;
       }
     }
-  #else
-    UNUSED(blink);
 
-    // Get the UTF8 character count of the string
-    uint8_t slen = utf8_strlen(lcd_status_message);
+  #else // !STATUS_MESSAGE_SCROLLING
+
+    UNUSED(blink);
 
     // Just print the string to the LCD
-    lcd_put_u8str_max(lcd_status_message, LCD_PIXEL_WIDTH);
+    lcd_put_u8str_max(status_message, LCD_PIXEL_WIDTH);
 
-    // Fill the rest with spaces if there are missing spaces
-    while (slen < LCD_WIDTH) {
-      lcd_put_wchar(' ');
-      ++slen;
-    }
-  #endif
+    // Fill the rest with spaces
+    for (; slen < LCD_WIDTH; ++slen) lcd_put_wchar(' ');
+
+  #endif // !STATUS_MESSAGE_SCROLLING
 }
 
-void lcd_impl_status_screen_0() {
+void MarlinUI::draw_status_screen() {
 
-  const bool blink = lcd_blink();
+  const bool blink = get_blink();
 
   // Status Menu Font
-  lcd_setFont(FONT_STATUSMENU);
+  set_font(FONT_STATUSMENU);
 
   //
   // Fan Animation
@@ -318,11 +309,9 @@ void lcd_impl_status_screen_0() {
         PROGRESS_BAR_WIDTH, 4
       );
 
-    #if DISABLED(LCD_SET_PROGRESS_MANUALLY)
-      const uint8_t progress_bar_percent = card.percentDone();
-    #endif
+    const uint8_t progress = get_progress();
 
-    if (progress_bar_percent > 1) {
+    if (progress > 1) {
 
       //
       // Progress bar solid part
@@ -331,7 +320,7 @@ void lcd_impl_status_screen_0() {
       if (PAGE_CONTAINS(50, 51))     // 50-51 (or just 50)
         u8g.drawBox(
           PROGRESS_BAR_X + 1, 50,
-          (uint16_t)((PROGRESS_BAR_WIDTH - 2) * progress_bar_percent * 0.01), 2
+          (uint16_t)((PROGRESS_BAR_WIDTH - 2) * progress * 0.01), 2
         );
 
       //
@@ -342,7 +331,7 @@ void lcd_impl_status_screen_0() {
         if (PAGE_CONTAINS(41, 48)) {
           // Percent complete
           lcd_moveto(55, 48);
-          lcd_put_u8str(itostr3(progress_bar_percent));
+          lcd_put_u8str(itostr3(progress));
           lcd_put_wchar('%');
         }
       #endif
@@ -449,11 +438,11 @@ void lcd_impl_status_screen_0() {
   #define EXTRAS_BASELINE 50
 
   if (PAGE_CONTAINS(EXTRAS_BASELINE - (INFO_FONT_HEIGHT - 1), EXTRAS_BASELINE)) {
-    lcd_setFont(FONT_MENU);
+    set_font(FONT_MENU);
     lcd_moveto(3, EXTRAS_BASELINE);
     lcd_put_wchar(LCD_STR_FEEDRATE[0]);
 
-    lcd_setFont(FONT_STATUSMENU);
+    set_font(FONT_STATUSMENU);
     lcd_moveto(12, EXTRAS_BASELINE);
     lcd_put_u8str(itostr3(feedrate_percentage));
     lcd_put_wchar('%');
@@ -467,7 +456,7 @@ void lcd_impl_status_screen_0() {
       lcd_moveto(102, EXTRAS_BASELINE);
       lcd_put_u8str(mstring);
       lcd_put_wchar('%');
-      lcd_setFont(FONT_MENU);
+      set_font(FONT_MENU);
       lcd_moveto(47, EXTRAS_BASELINE);
       lcd_put_wchar(LCD_STR_FILAM_DIA[0]); // lcd_put_u8str_P(PSTR(LCD_STR_FILAM_DIA));
       lcd_moveto(93, EXTRAS_BASELINE);
@@ -485,9 +474,9 @@ void lcd_impl_status_screen_0() {
     lcd_moveto(0, STATUS_BASELINE);
 
     #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
-      if (PENDING(millis(), previous_lcd_status_ms + 5000UL)) {  //Display both Status message line and Filament display on the last line
-        lcd_implementation_status_message(blink);
-      }
+      // Alternate Status message and Filament display
+      if (PENDING(millis(), next_filament_display))
+        draw_status_message(blink);
       else {
         lcd_put_u8str_P(PSTR(LCD_STR_FILAM_DIA));
         lcd_put_wchar(':');
@@ -498,7 +487,7 @@ void lcd_impl_status_screen_0() {
         lcd_put_wchar('%');
       }
     #else
-      lcd_implementation_status_message(blink);
+      draw_status_message(blink);
     #endif
   }
 }
diff --git a/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp b/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp
index 515adb9389..40c47868f9 100644
--- a/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp
+++ b/Marlin/src/lcd/dogm/status_screen_lite_ST7920.cpp
@@ -230,12 +230,8 @@ void ST7920_Lite_Status_Screen::load_cgram_icon(const uint16_t addr, const void
  */
 void ST7920_Lite_Status_Screen::draw_gdram_icon(uint8_t x, uint8_t y, const void *data) {
   const uint16_t *p_word = (const uint16_t *)data;
-  if (y > 2) { // Handle display folding
-    y -= 2;
-    x += 8;
-  }
-  --x;
-  --y;
+  // Handle display folding
+  if (y > 1) y -= 2, x += 8;
   for (int i = 0; i < 16; i++) {
     set_gdram_address(x, i + y * 16);
     begin_data();
@@ -398,24 +394,20 @@ const uint16_t feedrate_icon[] PROGMEM = {
 
 /************************** MAIN SCREEN *************************************/
 
-// The ST7920 does not have a degree character, but we
-// can fake it by writing it to GDRAM.
-// This function takes as an argument character positions
-// i.e x is [1-16], while the y position is [1-4]
-void ST7920_Lite_Status_Screen::draw_degree_symbol(uint8_t x, uint8_t y, bool draw) {
+/**
+ * The ST7920 has no degree character, so draw it to GDRAM.
+ * This function takes character position xy
+ * i.e., x is [0-15], while the y position is [0-3]
+ */
+void ST7920_Lite_Status_Screen::draw_degree_symbol(uint8_t x, uint8_t y, const bool draw) {
   const uint8_t *p_bytes = degree_symbol;
-    if (y > 2) {
-      // Handle display folding
-      y -= 2;
-      x += 16;
-    }
-    x -= 1;
-    y -= 1;
+    // Handle display folding
+    if (y > 1) y -= 2, x += 16;
     const bool    oddChar = x & 1;
-    const uint8_t x_word  = x >> 1;
-    const uint8_t y_top   = degree_symbol_y_top;
-    const uint8_t y_bot   = y_top + sizeof(degree_symbol)/sizeof(degree_symbol[0]);
-    for(uint8_t i = y_top; i < y_bot; i++) {
+    const uint8_t x_word  = x >> 1,
+                  y_top   = degree_symbol_y_top,
+                  y_bot   = y_top + sizeof(degree_symbol)/sizeof(degree_symbol[0]);
+    for (uint8_t i = y_top; i < y_bot; i++) {
       uint8_t byte = pgm_read_byte(p_bytes++);
       set_gdram_address(x_word, i + y * 16);
       begin_data();
@@ -438,14 +430,14 @@ void ST7920_Lite_Status_Screen::draw_static_elements() {
   load_cgram_icon(CGRAM_ICON_4_ADDR, fan2_icon);
 
   // Draw the static icons in GDRAM
-  draw_gdram_icon(1, 1, nozzle_icon);
+  draw_gdram_icon(0, 0, nozzle_icon);
   #if HOTENDS > 1
-    draw_gdram_icon(1,2,nozzle_icon);
-    draw_gdram_icon(1,3,bed_icon);
+    draw_gdram_icon(0, 1, nozzle_icon);
+    draw_gdram_icon(0, 2, bed_icon);
   #else
-    draw_gdram_icon(1,2,bed_icon);
+    draw_gdram_icon(0, 1, bed_icon);
   #endif
-  draw_gdram_icon(6,2,feedrate_icon);
+  draw_gdram_icon(5, 1, feedrate_icon);
 
   // Draw the initial fan icon
   draw_fan_icon(false);
@@ -462,15 +454,15 @@ void ST7920_Lite_Status_Screen::draw_static_elements() {
 void ST7920_Lite_Status_Screen::draw_progress_bar(const uint8_t value) {
   #if HOTENDS == 1
     // If we have only one extruder, draw a long progress bar on the third line
-    const uint8_t top     = 1,         // Top in pixels
-                  bottom  = 13,        // Bottom in pixels
-                  left    = 12,        // Left edge, in 16-bit words
-                  width   = 4;         // Width of progress bar, in 16-bit words
+    constexpr uint8_t top     = 1,         // Top in pixels
+                      bottom  = 13,        // Bottom in pixels
+                      left    = 12,        // Left edge, in 16-bit words
+                      width   = 4;         // Width of progress bar, in 16-bit words
   #else
-    const uint8_t top     = 16 + 1,
-                  bottom  = 16 + 13,
-                  left    = 5,
-                  width   = 3;
+    constexpr uint8_t top     = 16 + 1,
+                      bottom  = 16 + 13,
+                      left    = 5,
+                      width   = 3;
   #endif
   const uint8_t char_pcnt  = 100 / width; // How many percent does each 16-bit word represent?
 
@@ -557,10 +549,10 @@ static struct {
 
 void ST7920_Lite_Status_Screen::draw_temps(uint8_t line, const int16_t temp, const int16_t target, bool showTarget, bool targetStateChange) {
   switch (line) {
-    case 1: set_ddram_address(DDRAM_LINE_1 + 1); break;
-    case 2: set_ddram_address(DDRAM_LINE_2 + 1); break;
+    case 0: set_ddram_address(DDRAM_LINE_1 + 1); break;
+    case 1: set_ddram_address(DDRAM_LINE_2 + 1); break;
+    case 2: set_ddram_address(DDRAM_LINE_3 + 1); break;
     case 3: set_ddram_address(DDRAM_LINE_3 + 1); break;
-    case 4: set_ddram_address(DDRAM_LINE_3 + 1); break;
   }
   begin_data();
   write_number(temp);
@@ -572,27 +564,27 @@ void ST7920_Lite_Status_Screen::draw_temps(uint8_t line, const int16_t temp, con
 
   if (targetStateChange) {
     if (!showTarget) write_str(F("    "));
-    draw_degree_symbol(6,  line, !showTarget);
-    draw_degree_symbol(10, line, showTarget);
+    draw_degree_symbol(5, line, !showTarget);
+    draw_degree_symbol(9, line,  showTarget);
   }
 }
 
 void ST7920_Lite_Status_Screen::draw_extruder_1_temp(const int16_t temp, const int16_t target, bool forceUpdate) {
   const bool show_target = target && FAR(temp, target);
-  draw_temps(1, temp, target, show_target, display_state.E1_show_target != show_target || forceUpdate);
+  draw_temps(0, temp, target, show_target, display_state.E1_show_target != show_target || forceUpdate);
   display_state.E1_show_target = show_target;
 }
 
 void ST7920_Lite_Status_Screen::draw_extruder_2_temp(const int16_t temp, const int16_t target, bool forceUpdate) {
   const bool show_target = target && FAR(temp, target);
-  draw_temps(2, temp, target, show_target, display_state.E2_show_target != show_target || forceUpdate);
+  draw_temps(1, temp, target, show_target, display_state.E2_show_target != show_target || forceUpdate);
   display_state.E2_show_target = show_target;
 }
 
 #if HAS_HEATED_BED
   void ST7920_Lite_Status_Screen::draw_bed_temp(const int16_t temp, const int16_t target, bool forceUpdate) {
     const bool show_target = target && FAR(temp, target);
-    draw_temps(2
+    draw_temps(1
       #if HOTENDS > 1
         + 1
       #endif
@@ -632,44 +624,38 @@ void ST7920_Lite_Status_Screen::draw_feedrate_percentage(const uint16_t percenta
   #endif
 }
 
-void ST7920_Lite_Status_Screen::draw_status_message(const char *str) {
+void ST7920_Lite_Status_Screen::draw_status_message() {
+  const char *str = ui.status_message;
+
   set_ddram_address(DDRAM_LINE_4);
   begin_data();
-  const uint8_t lcd_len = 16;
   #if ENABLED(STATUS_MESSAGE_SCROLLING)
 
     uint8_t slen = utf8_strlen(str);
 
-    // If the string fits into the LCD, just print it and do not scroll it
-    if (slen <= lcd_len) {
-
-      // The string isn't scrolling and may not fill the screen
+    if (slen <= LCD_WIDTH) {
+      // String fits the LCD, so just print it
       write_str(str);
-
-      // Fill the rest with spaces
-      while (slen < lcd_len) {
-        write_byte(' ');
-        ++slen;
-      }
+      for (; slen < LCD_WIDTH; ++slen) write_byte(' ');
     }
     else {
       // String is larger than the available space in screen.
 
       // Get a pointer to the next valid UTF8 character
-      const char *stat = str + status_scroll_offset;
+      const char *stat = str + ui.status_scroll_offset;
 
       // Get the string remaining length
       const uint8_t rlen = utf8_strlen(stat);
 
       // If we have enough characters to display
-      if (rlen >= lcd_len) {
+      if (rlen >= LCD_WIDTH) {
         // The remaining string fills the screen - Print it
-        write_str(stat, lcd_len);
+        write_str(stat, LCD_WIDTH);
       }
       else {
         // The remaining string does not completely fill the screen
         write_str(stat);                        // The string leaves space
-        uint8_t chars = lcd_len - rlen;         // Amount of space left in characters
+        uint8_t chars = LCD_WIDTH - rlen;         // Amount of space left in characters
 
         write_byte('.');                        // Always at 1+ spaces left, draw a dot
         if (--chars) {                          // Draw a second dot if there's space
@@ -680,26 +666,21 @@ void ST7920_Lite_Status_Screen::draw_status_message(const char *str) {
       }
 
       // Adjust by complete UTF8 characters
-      if (status_scroll_offset < slen) {
-        status_scroll_offset++;
-        while (!START_OF_UTF8_CHAR(str[status_scroll_offset]))
-          status_scroll_offset++;
+      if (ui.status_scroll_offset < slen) {
+        ui.status_scroll_offset++;
+        while (!START_OF_UTF8_CHAR(str[ui.status_scroll_offset]))
+          ui.status_scroll_offset++;
       }
       else
-        status_scroll_offset = 0;
+        ui.status_scroll_offset = 0;
     }
+
   #else
-    // Get the UTF8 character count of the string
-    uint8_t slen = utf8_strlen(str);
 
-    // Just print the string to the LCD
-    write_str(str, lcd_len);
+    uint8_t slen = utf8_strlen(str);
+    write_str(str, LCD_WIDTH);
+    for (; slen < LCD_WIDTH; ++slen) write_byte(' ');
 
-    // Fill the rest with spaces if there are missing spaces
-    while (slen < lcd_len) {
-      write_byte(' ');
-      ++slen;
-    }
   #endif
 }
 
@@ -709,7 +690,7 @@ void ST7920_Lite_Status_Screen::draw_position(const float x, const float y, cons
   begin_data();
 
   // If position is unknown, flash the labels.
-  const unsigned char alt_label = position_known ? 0 : (lcd_blink() ? ' ' : 0);
+  const unsigned char alt_label = position_known ? 0 : (ui.get_blink() ? ' ' : 0);
 
   dtostrf(x, -4, 0, str);
   write_byte(alt_label ? alt_label : 'X');
@@ -728,7 +709,7 @@ bool ST7920_Lite_Status_Screen::indicators_changed() {
   // We only add the target temperatures to the checksum
   // because the actual temps fluctuate so by updating
   // them only during blinks we gain a bit of stability.
-  const bool       blink             = lcd_blink();
+  const bool       blink             = ui.get_blink();
   const uint16_t   feedrate_perc     = feedrate_percentage;
   const uint8_t    fs                = (((uint16_t)fan_speed[0] + 1) * 100) / 256;
   const int16_t    extruder_1_target = thermalManager.degTargetHotend(0);
@@ -754,7 +735,7 @@ bool ST7920_Lite_Status_Screen::indicators_changed() {
 
 void ST7920_Lite_Status_Screen::update_indicators(const bool forceUpdate) {
   if (forceUpdate || indicators_changed()) {
-    const bool       blink             = lcd_blink();
+    const bool       blink             = ui.get_blink();
     const duration_t elapsed           = print_job_timer.duration();
     const uint16_t   feedrate_perc     = feedrate_percentage;
     const uint8_t    fs                = (((uint16_t)fan_speed[0] + 1) * 100) / 256;
@@ -783,41 +764,32 @@ void ST7920_Lite_Status_Screen::update_indicators(const bool forceUpdate) {
     // Update the fan and bed animations
     if (fs) draw_fan_icon(blink);
     #if HAS_HEATED_BED
-      if (bed_target > 0)
-        draw_heat_icon(blink, true);
-      else
-        draw_heat_icon(false, false);
+      draw_heat_icon(bed_target > 0 && blink, bed_target > 0);
     #endif
   }
 }
 
 bool ST7920_Lite_Status_Screen::position_changed() {
-  const float x_pos = current_position[X_AXIS],
-              y_pos = current_position[Y_AXIS],
-              z_pos = current_position[Z_AXIS];
+  const float x_pos = current_position[X_AXIS], y_pos = current_position[Y_AXIS], z_pos = current_position[Z_AXIS];
   const uint8_t checksum = uint8_t(x_pos) ^ uint8_t(y_pos) ^ uint8_t(z_pos);
-
-  static uint8_t last_checksum = 0;
-  if (last_checksum == checksum) return false;
-  last_checksum = checksum;
-  return true;
+  static uint8_t last_checksum = 0, changed = last_checksum != checksum;
+  if (changed) last_checksum = checksum;
+  return changed;
 }
 
 bool ST7920_Lite_Status_Screen::status_changed() {
   uint8_t checksum = 0;
-  for (const char *p = lcd_status_message; *p; p++) checksum ^= *p;
-  static uint8_t last_checksum = 0;
-  if (last_checksum == checksum) return false;
-  last_checksum = checksum;
-  return true;
+  for (const char *p = ui.status_message; *p; p++) checksum ^= *p;
+  static uint8_t last_checksum = 0, changed = last_checksum != checksum;
+  if (changed) last_checksum = checksum;
+  return changed;
 }
 
 bool ST7920_Lite_Status_Screen::blink_changed() {
   static uint8_t last_blink = 0;
-  const bool blink = lcd_blink();
-  if (last_blink == blink) return false;
-  last_blink = blink;
-  return true;
+  const bool blink = ui.get_blink(), changed = last_blink != blink;
+  if (changed) last_blink = blink;
+  return changed;
 }
 
 #ifndef STATUS_EXPIRE_SECONDS
@@ -831,60 +803,56 @@ void ST7920_Lite_Status_Screen::update_status_or_position(bool forceUpdate) {
   #endif
 
   /**
-   * There is only enough room in the display for either the
-   * status message or the position, not both, so we choose
-   * one or another. Whenever the status message changes,
-   * we show it for a number of consecutive seconds, but
-   * then go back to showing the position as soon as the
-   * head moves, i.e:
+   * There's only enough room for either the status message or the position,
+   * so draw one or the other. When the status message changes, show it for
+   * a few seconds, then return to the position display once the head moves.
    *
-   *    countdown > 1    -- Show status
-   *    countdown = 1    -- Show status, until movement
-   *    countdown = 0    -- Show position
+   *  countdown > 1  -- Show status
+   *  countdown = 1  -- Show status, until movement
+   *  countdown = 0  -- Show position
    *
-   * If STATUS_EXPIRE_SECONDS is zero, the position display
-   * will be disabled and only the status will be shown.
+   * If STATUS_EXPIRE_SECONDS is zero, only the status is shown.
    */
   if (forceUpdate || status_changed()) {
     #if ENABLED(STATUS_MESSAGE_SCROLLING)
-      status_scroll_offset = 0;
+      ui.status_scroll_offset = 0;
     #endif
     #if STATUS_EXPIRE_SECONDS
-      countdown = lcd_status_message[0] ? STATUS_EXPIRE_SECONDS : 0;
+      countdown = ui.status_message[0] ? STATUS_EXPIRE_SECONDS : 0;
     #endif
-    draw_status_message(lcd_status_message);
+    draw_status_message();
     blink_changed(); // Clear changed flag
   }
   #if !STATUS_EXPIRE_SECONDS
     #if ENABLED(STATUS_MESSAGE_SCROLLING)
       else
-        draw_status_message(lcd_status_message);
+        draw_status_message();
     #endif
   #else
-    else if (countdown > 1 && blink_changed()) {
-      countdown--;
-      #if ENABLED(STATUS_MESSAGE_SCROLLING)
-        draw_status_message(lcd_status_message);
-      #endif
-    }
-    else if (countdown > 0 && blink_changed()) {
-      if (position_changed()) {
+    else if (blink_changed()) {
+      if (countdown > 1) {
         countdown--;
-        forceUpdate = true;
+        #if ENABLED(STATUS_MESSAGE_SCROLLING)
+          draw_status_message();
+        #endif
+      }
+      else if (countdown > 0) {
+        if (position_changed()) {
+          countdown--;
+          forceUpdate = true;
+        }
+        #if ENABLED(STATUS_MESSAGE_SCROLLING)
+          draw_status_message();
+        #endif
       }
-      #if ENABLED(STATUS_MESSAGE_SCROLLING)
-        draw_status_message(lcd_status_message);
-      #endif
     }
+
     if (countdown == 0 && (forceUpdate || position_changed() ||
       #if DISABLED(DISABLE_REDUCED_ACCURACY_WARNING)
         blink_changed()
       #endif
     )) {
-      draw_position(
-        current_position[X_AXIS],
-        current_position[Y_AXIS],
-        current_position[Z_AXIS],
+      draw_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS],
         #if ENABLED(DISABLE_REDUCED_ACCURACY_WARNING)
           true
         #else
@@ -898,24 +866,16 @@ void ST7920_Lite_Status_Screen::update_status_or_position(bool forceUpdate) {
 void ST7920_Lite_Status_Screen::update_progress(const bool forceUpdate) {
   #if ENABLED(LCD_SET_PROGRESS_MANUALLY) || ENABLED(SDSUPPORT)
 
-    #if DISABLED(LCD_SET_PROGRESS_MANUALLY)
-      uint8_t progress_bar_percent = 0;
-    #endif
-
-    #if ENABLED(SDSUPPORT)
-      // Progress bar % comes from SD when actively printing
-      if (IS_SD_PRINTING()) progress_bar_percent = card.percentDone();
-    #endif
-
     // Since the progress bar involves writing
     // quite a few bytes to GDRAM, only do this
     // when an update is actually necessary.
 
     static uint8_t last_progress = 0;
-    if (!forceUpdate && last_progress == progress_bar_percent) return;
-    last_progress = progress_bar_percent;
-
-    draw_progress_bar(progress_bar_percent);
+    const uint8_t progress = ui.get_progress();
+    if (forceUpdate || last_progress != progress) {
+      last_progress = progress;
+      draw_progress_bar(progress);
+    }
 
   #else
 
@@ -966,7 +926,7 @@ void ST7920_Lite_Status_Screen::clear_text_buffer() {
   ncs();
 }
 
-void lcd_impl_status_screen_0() {
+void MarlinUI::draw_status_screen() {
   ST7920_Lite_Status_Screen::update(false);
 }
 
diff --git a/Marlin/src/lcd/dogm/status_screen_lite_ST7920_class.h b/Marlin/src/lcd/dogm/status_screen_lite_ST7920_class.h
index 323d951c36..79cc1b1fd8 100644
--- a/Marlin/src/lcd/dogm/status_screen_lite_ST7920_class.h
+++ b/Marlin/src/lcd/dogm/status_screen_lite_ST7920_class.h
@@ -74,7 +74,7 @@ class ST7920_Lite_Status_Screen {
     static uint8_t string_checksum(const char *str);
 
   protected:
-    static void draw_degree_symbol(uint8_t x, uint8_t y, bool draw);
+    static void draw_degree_symbol(uint8_t x, uint8_t y, const bool draw);
     static void draw_static_elements();
     static void draw_progress_bar(const uint8_t value);
     static void draw_fan_icon(const bool whichIcon);
@@ -86,7 +86,7 @@ class ST7920_Lite_Status_Screen {
     static void draw_fan_speed(const uint8_t value);
     static void draw_print_time(const duration_t &elapsed);
     static void draw_feedrate_percentage(const uint16_t percentage);
-    static void draw_status_message(const char *str);
+    static void draw_status_message();
     static void draw_position(const float x, const float y, const float z, bool position_known = true);
 
     static bool indicators_changed();
diff --git a/Marlin/src/lcd/dogm/ultralcd_impl_DOGM.cpp b/Marlin/src/lcd/dogm/ultralcd_impl_DOGM.cpp
index 47a70093f4..4d0b94b9d7 100644
--- a/Marlin/src/lcd/dogm/ultralcd_impl_DOGM.cpp
+++ b/Marlin/src/lcd/dogm/ultralcd_impl_DOGM.cpp
@@ -75,16 +75,16 @@ U8GLIB *pu8g = &u8g;
 
 #if HAS_LCD_CONTRAST
 
-  int16_t lcd_contrast; // Initialized by settings.load()
+  int16_t MarlinUI::contrast; // Initialized by settings.load()
 
-  void set_lcd_contrast(const int16_t value) {
-    lcd_contrast = constrain(value, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX);
-    u8g.setContrast(lcd_contrast);
+  void MarlinUI::set_contrast(const int16_t value) {
+    contrast = constrain(value, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX);
+    u8g.setContrast(contrast);
   }
 
 #endif
 
-void lcd_setFont(const MarlinFont font_nr) {
+void MarlinUI::set_font(const MarlinFont font_nr) {
   static char currentfont = 0;
   if (font_nr != currentfont) {
     switch ((currentfont = font_nr)) {
@@ -141,7 +141,7 @@ void lcd_setFont(const MarlinFont font_nr) {
 
   #endif // SHOW_CUSTOM_BOOTSCREEN
 
-  void lcd_bootscreen() {
+  void MarlinUI::show_bootscreen() {
     #if ENABLED(SHOW_CUSTOM_BOOTSCREEN)
       lcd_custom_bootscreen();
     #endif
@@ -160,7 +160,7 @@ void lcd_setFont(const MarlinFont font_nr) {
     u8g.firstPage();
     do {
       u8g.drawBitmapP(offx, offy, (START_BMPWIDTH + 7) / 8, START_BMPHEIGHT, start_bmp);
-      lcd_setFont(FONT_MENU);
+      ui.set_font(FONT_MENU);
       #ifndef STRING_SPLASH_LINE2
         const uint8_t txt1X = width - (sizeof(STRING_SPLASH_LINE1) - 1) * (MENU_FONT_WIDTH);
         u8g.drawStr(txt1X, (height + MENU_FONT_HEIGHT) / 2, STRING_SPLASH_LINE1);
@@ -181,7 +181,7 @@ void lcd_setFont(const MarlinFont font_nr) {
 #endif
 
 // Initialize or re-initialize the LCD
-void lcd_implementation_init() {
+void MarlinUI::init_lcd() {
 
   #if PIN_EXISTS(LCD_BACKLIGHT) // Enable LCD backlight
     OUT_WRITE(LCD_BACKLIGHT_PIN, HIGH);
@@ -206,7 +206,7 @@ void lcd_implementation_init() {
   #endif
 
   #if HAS_LCD_CONTRAST
-    set_lcd_contrast(lcd_contrast);
+    refresh_contrast();
   #endif
 
   #if ENABLED(LCD_SCREEN_ROT_90)
@@ -221,16 +221,16 @@ void lcd_implementation_init() {
 }
 
 // The kill screen is displayed for unrecoverable conditions
-void lcd_kill_screen() {
+void MarlinUI::draw_kill_screen() {
   #if ENABLED(LIGHTWEIGHT_UI)
     ST7920_Lite_Status_Screen::clear_text_buffer();
   #endif
   const uint8_t h4 = u8g.getHeight() / 4;
   u8g.firstPage();
   do {
-    lcd_setFont(FONT_MENU);
+    set_font(FONT_MENU);
     lcd_moveto(0, h4 * 1);
-    lcd_put_u8str(lcd_status_message);
+    lcd_put_u8str(status_message);
     lcd_moveto(0, h4 * 2);
     lcd_put_u8str_P(PSTR(MSG_HALTED));
     lcd_moveto(0, h4 * 3);
@@ -238,7 +238,7 @@ void lcd_kill_screen() {
   } while (u8g.nextPage());
 }
 
-void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
+void MarlinUI::clear_lcd() { } // Automatically cleared by Picture Loop
 
 #if HAS_LCD_MENU
 
@@ -246,7 +246,7 @@ void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
 
   #if ENABLED(ADVANCED_PAUSE_FEATURE)
 
-    void lcd_implementation_hotend_status(const uint8_t row, const uint8_t extruder) {
+    void MarlinUI::draw_hotend_status(const uint8_t row, const uint8_t extruder) {
       row_y1 = row * (MENU_FONT_HEIGHT) + 1;
       row_y2 = row_y1 + MENU_FONT_HEIGHT - 1;
 
@@ -259,7 +259,7 @@ void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
       lcd_put_u8str(itostr3(thermalManager.degHotend(extruder)));
       lcd_put_wchar('/');
 
-      if (lcd_blink() || !thermalManager.is_heater_idle(extruder))
+      if (get_blink() || !thermalManager.is_heater_idle(extruder))
         lcd_put_u8str(itostr3(thermalManager.degTargetHotend(extruder)));
     }
 
@@ -295,7 +295,7 @@ void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
   }
 
   // Draw a static line of text in the same idiom as a menu item
-  void lcd_implementation_drawmenu_static(const uint8_t row, PGM_P pstr, const bool center/*=true*/, const bool invert/*=false*/, const char* valstr/*=NULL*/) {
+  void draw_menu_item_static(const uint8_t row, PGM_P pstr, const bool center/*=true*/, const bool invert/*=false*/, const char* valstr/*=NULL*/) {
 
     if (mark_as_selected(row, invert)) {
 
@@ -315,7 +315,7 @@ void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
   }
 
   // Draw a generic menu item
-  void lcd_implementation_drawmenu_generic(const bool isSelected, const uint8_t row, PGM_P pstr, const char pre_char, const char post_char) {
+  void draw_menu_item_generic(const bool isSelected, const uint8_t row, PGM_P const pstr, const char pre_char, const char post_char) {
     UNUSED(pre_char);
 
     if (mark_as_selected(row, isSelected)) {
@@ -330,7 +330,7 @@ void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
   }
 
   // Draw a menu item with an editable value
-  void _drawmenu_setting_edit_generic(const bool isSelected, const uint8_t row, PGM_P pstr, const char* const data, const bool pgm) {
+  void _drawmenu_setting_edit_generic(const bool isSelected, const uint8_t row, PGM_P const pstr, const char* const data, const bool pgm) {
     if (mark_as_selected(row, isSelected)) {
       const uint8_t vallen = (pgm ? utf8_strlen_P(data) : utf8_strlen((char*)data));
       uint8_t n = LCD_WIDTH - 2 - vallen;
@@ -343,7 +343,7 @@ void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
     }
   }
 
-  void lcd_implementation_drawedit(PGM_P const pstr, const char* const value/*=NULL*/) {
+  void draw_edit_screen(PGM_P const pstr, const char* const value/*=NULL*/) {
     const uint8_t labellen = utf8_strlen_P(pstr), vallen = utf8_strlen(value);
 
     bool extra_row = labellen > LCD_WIDTH - 2 - vallen;
@@ -356,12 +356,12 @@ void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
         if (labellen + vallen + 1 > lcd_edit_width) extra_row = true;
         lcd_chr_fit = lcd_edit_width + 1;
         one_chr_width = EDIT_FONT_WIDTH;
-        lcd_setFont(FONT_EDIT);
+        ui.set_font(FONT_EDIT);
       }
       else {
         lcd_chr_fit = LCD_WIDTH;
         one_chr_width = MENU_FONT_WIDTH;
-        lcd_setFont(FONT_MENU);
+        ui.set_font(FONT_MENU);
       }
     #else
       constexpr uint8_t lcd_chr_fit = LCD_WIDTH,
@@ -397,7 +397,7 @@ void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
 
   #if ENABLED(SDSUPPORT)
 
-    void _drawmenu_sd(const bool isSelected, const uint8_t row, PGM_P const pstr, CardReader &theCard, const bool isDir) {
+    void draw_sd_menu_item(const bool isSelected, const uint8_t row, PGM_P const pstr, CardReader &theCard, const bool isDir) {
       UNUSED(pstr);
 
       mark_as_selected(row, isSelected);
@@ -415,11 +415,11 @@ void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
               name_hash = ((name_hash << 1) | (name_hash >> 7)) ^ theCard.filename[l];  // rotate, xor
             if (filename_scroll_hash != name_hash) {                            // If the hash changed...
               filename_scroll_hash = name_hash;                                 // Save the new hash
-              filename_scroll_max = MAX(0, utf8_strlen(theCard.longFilename) - maxlen); // Update the scroll limit
-              filename_scroll_pos = 0;                                          // Reset scroll to the start
-              lcd_status_update_delay = 8;                                      // Don't scroll right away
+              ui.filename_scroll_max = MAX(0, utf8_strlen(theCard.longFilename) - maxlen); // Update the scroll limit
+              ui.filename_scroll_pos = 0;                                       // Reset scroll to the start
+              ui.lcd_status_update_delay = 8;                                   // Don't scroll right away
             }
-            outstr += filename_scroll_pos;
+            outstr += ui.filename_scroll_pos;
           }
         #else
           theCard.longFilename[maxlen] = '\0'; // cutoff at screen edge
@@ -428,8 +428,7 @@ void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
 
       if (isDir) lcd_put_wchar(LCD_STR_FOLDER[0]);
 
-      int n;
-      n = lcd_put_u8str_max(outstr, maxlen * (MENU_FONT_WIDTH));
+      uint8_t n = lcd_put_u8str_max(outstr, maxlen * (MENU_FONT_WIDTH));
       n = maxlen * (MENU_FONT_WIDTH) - n;
       while (n - MENU_FONT_WIDTH > 0) { n -= lcd_put_wchar(' '); }
     }
@@ -446,7 +445,7 @@ void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
     #define MAP_MAX_PIXELS_X        53
     #define MAP_MAX_PIXELS_Y        49
 
-    void lcd_implementation_ubl_plot(const uint8_t x_plot, const uint8_t y_plot) {
+    void MarlinUI::ubl_plot(const uint8_t x_plot, const uint8_t y_plot) {
       // Scale the box pixels appropriately
       uint8_t x_map_pixels = ((MAP_MAX_PIXELS_X - 4) / (GRID_MAX_POINTS_X)) * (GRID_MAX_POINTS_X),
               y_map_pixels = ((MAP_MAX_PIXELS_Y - 4) / (GRID_MAX_POINTS_Y)) * (GRID_MAX_POINTS_Y),
diff --git a/Marlin/src/lcd/extensible_ui/ui_api.cpp b/Marlin/src/lcd/extensible_ui/ui_api.cpp
index 8d329642c0..343910e13b 100644
--- a/Marlin/src/lcd/extensible_ui/ui_api.cpp
+++ b/Marlin/src/lcd/extensible_ui/ui_api.cpp
@@ -681,14 +681,14 @@ namespace UI {
 
 // At the moment, we piggy-back off the ultralcd calls, but this could be cleaned up in the future
 
-void lcd_init() {
+void MarlinUI::init() {
   #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
     SET_INPUT_PULLUP(SD_DETECT_PIN);
   #endif
   UI::onStartup();
 }
 
-void lcd_update() {
+void MarlinUI::update() {
   #if ENABLED(SDSUPPORT)
     static bool last_sd_status;
     const bool sd_status = IS_SD_INSERTED();
@@ -712,15 +712,15 @@ void lcd_update() {
   UI::onIdle();
 }
 
-bool lcd_hasstatus() { return true; }
-bool lcd_detected() { return true; }
-void lcd_reset_alert_level() { }
-void lcd_refresh() { }
-void lcd_setstatus(const char * const message, const bool persist /* = false */) { UI::onStatusChanged(message); }
-void lcd_setstatusPGM(const char * const message, int8_t level /* = 0 */)        { UI::onStatusChanged((progmem_str)message); }
-void lcd_setalertstatusPGM(const char * const message)                           { lcd_setstatusPGM(message, 0); }
+bool MarlinUI::hasstatus() { return true; }
+bool MarlinUI::detected() { return true; }
+void MarlinUI::reset_alert_level() { }
+void MarlinUI::refresh() { }
+void MarlinUI::setstatus(const char * const message, const bool persist /* = false */) { UI::onStatusChanged(message); }
+void MarlinUI::setstatusPGM(const char * const message, int8_t level /* = 0 */)        { UI::onStatusChanged((progmem_str)message); }
+void MarlinUI::setalertstatusPGM(const char * const message)                    { setstatusPGM(message, 0); }
 
-void lcd_reset_status() {
+void MarlinUI::reset_status() {
   static const char paused[] PROGMEM = MSG_PRINT_PAUSED;
   static const char printing[] PROGMEM = MSG_PRINTING;
   static const char welcome[] PROGMEM = WELCOME_MSG;
@@ -729,17 +729,17 @@ void lcd_reset_status() {
     msg = paused;
   #if ENABLED(SDSUPPORT)
     else if (IS_SD_PRINTING())
-      return lcd_setstatus(card.longest_filename(), true);
+      return setstatus(card.longest_filename(), true);
   #endif
   else if (print_job_timer.isRunning())
     msg = printing;
   else
     msg = welcome;
 
-  lcd_setstatusPGM(msg, -1);
+  setstatusPGM(msg, -1);
 }
 
-void lcd_status_printf_P(const uint8_t level, const char * const fmt, ...) {
+void MarlinUI::status_printf_P(const uint8_t level, const char * const fmt, ...) {
   char buff[64];
   va_list args;
   va_start(args, fmt);
@@ -749,7 +749,7 @@ void lcd_status_printf_P(const uint8_t level, const char * const fmt, ...) {
   UI::onStatusChanged(buff);
 }
 
-void kill_screen(PGM_P msg) {
+void MarlinUI::kill_screen(PGM_P const msg) {
   if (!flags.printer_killed) {
     flags.printer_killed = true;
     UI::onPrinterKilled(msg);
diff --git a/Marlin/src/lcd/language/language_es.h b/Marlin/src/lcd/language/language_es.h
index 0852215550..c7c193580c 100644
--- a/Marlin/src/lcd/language/language_es.h
+++ b/Marlin/src/lcd/language/language_es.h
@@ -204,7 +204,7 @@
 #define MSG_INFO_PROTOCOL                   _UxGT("Protocolo")
 #define MSG_CASE_LIGHT                      _UxGT("Luz cabina")
 
-#if LCD_WIDTH > 19
+#if LCD_WIDTH >= 20
   #define MSG_INFO_PRINT_COUNT              _UxGT("Conteo de impresión")
   #define MSG_INFO_COMPLETED_PRINTS         _UxGT("Completadas")
   #define MSG_INFO_PRINT_TIME               _UxGT("Tiempo total de imp.")
diff --git a/Marlin/src/lcd/language/language_zh_CN.h b/Marlin/src/lcd/language/language_zh_CN.h
index adee5d5bdb..fcdaecc861 100644
--- a/Marlin/src/lcd/language/language_zh_CN.h
+++ b/Marlin/src/lcd/language/language_zh_CN.h
@@ -324,7 +324,7 @@
 #define MSG_CASE_LIGHT                      _UxGT("外壳灯") // "Case light"
 #define MSG_CASE_LIGHT_BRIGHTNESS           _UxGT("灯亮度") // "Light BRIGHTNESS"
 
-#if LCD_WIDTH > 19
+#if LCD_WIDTH >= 20
   #define MSG_INFO_PRINT_COUNT              _UxGT("打印计数")  //"Print Count"
   #define MSG_INFO_COMPLETED_PRINTS         _UxGT("完成了")  //"Completed"
   #define MSG_INFO_PRINT_TIME               _UxGT("总打印时间")  //"Total print time"
diff --git a/Marlin/src/lcd/language/language_zh_TW.h b/Marlin/src/lcd/language/language_zh_TW.h
index c64ac2f2b6..5f28b48d0b 100644
--- a/Marlin/src/lcd/language/language_zh_TW.h
+++ b/Marlin/src/lcd/language/language_zh_TW.h
@@ -324,7 +324,7 @@
 #define MSG_CASE_LIGHT                      _UxGT("外殼燈") // "Case light"
 #define MSG_CASE_LIGHT_BRIGHTNESS           _UxGT("燈亮度") // "Light BRIGHTNESS"
 
-#if LCD_WIDTH > 19
+#if LCD_WIDTH >= 20
   #define MSG_INFO_PRINT_COUNT              _UxGT("列印計數")  //"Print Count"
   #define MSG_INFO_COMPLETED_PRINTS         _UxGT("已完成")  //"Completed"
   #define MSG_INFO_PRINT_TIME               _UxGT("總列印時間")  //"Total print time"
diff --git a/Marlin/src/lcd/malyanlcd.cpp b/Marlin/src/lcd/malyanlcd.cpp
index 1e90cea24a..efc46f42e2 100644
--- a/Marlin/src/lcd/malyanlcd.cpp
+++ b/Marlin/src/lcd/malyanlcd.cpp
@@ -417,7 +417,7 @@ void update_usb_status(const bool forceUpdate) {
  * The optimize attribute fixes a register Compile
  * error for amtel.
  */
-void lcd_update() {
+void MarlinUI::update() {
   static char inbound_buffer[MAX_CURLY_COMMAND];
 
   // First report USB status.
@@ -461,7 +461,7 @@ void lcd_update() {
  * it and translate into gcode, which then gets injected into
  * the command queue where possible.
  */
-void lcd_init() {
+void MarlinUI::init() {
   inbound_count = 0;
   LCD_SERIAL.begin(500000);
 
@@ -479,7 +479,7 @@ void lcd_init() {
 /**
  * Set an alert.
  */
-void lcd_setalertstatusPGM(PGM_P message) {
+void MarlinUI::setalertstatusPGM(PGM_P message) {
   char message_buffer[MAX_CURLY_COMMAND];
   sprintf_P(message_buffer, PSTR("{E:%s}"), message);
   write_to_lcd(message_buffer);
diff --git a/Marlin/src/lcd/menu/menu.cpp b/Marlin/src/lcd/menu/menu.cpp
index 346a81729d..e3d9f09d49 100644
--- a/Marlin/src/lcd/menu/menu.cpp
+++ b/Marlin/src/lcd/menu/menu.cpp
@@ -30,6 +30,7 @@
 #include "../../module/motion.h"
 #include "../../gcode/queue.h"
 #include "../../sd/cardreader.h"
+#include "../../libs/buzzer.h"
 
 #if ENABLED(EEPROM_SETTINGS)
   #include "../../module/configuration_store.h"
@@ -61,10 +62,6 @@ menuPosition screen_history[6];
 uint8_t screen_history_depth = 0;
 bool screen_changed;
 
-#if LCD_TIMEOUT_TO_STATUS
-  bool defer_return_to_status;
-#endif
-
 // Value Editing
 PGM_P editLabel;
 void *editValue;
@@ -79,9 +76,9 @@ bool no_reentry = false;
 //////// Menu Navigation & History /////////
 ////////////////////////////////////////////
 
-void lcd_return_to_status() { lcd_goto_screen(lcd_status_screen); }
+void MarlinUI::return_to_status() { goto_screen(status_screen); }
 
-void lcd_save_previous_screen() {
+void MarlinUI::save_previous_screen() {
   if (screen_history_depth < COUNT(screen_history)) {
     screen_history[screen_history_depth].menu_function = currentScreen;
     screen_history[screen_history_depth].encoder_position = encoderPosition;
@@ -89,25 +86,18 @@ void lcd_save_previous_screen() {
   }
 }
 
-void lcd_goto_previous_menu() {
+void MarlinUI::goto_previous_screen() {
   if (screen_history_depth > 0) {
     --screen_history_depth;
-    lcd_goto_screen(
+    goto_screen(
       screen_history[screen_history_depth].menu_function,
       screen_history[screen_history_depth].encoder_position
     );
   }
   else
-    lcd_return_to_status();
+    return_to_status();
 }
 
-#if LCD_TIMEOUT_TO_STATUS
-  void lcd_goto_previous_menu_no_defer() {
-    set_defer_return_to_status(false);
-    lcd_goto_previous_menu();
-  }
-#endif
-
 ////////////////////////////////////////////
 /////////// Common Menu Actions ////////////
 ////////////////////////////////////////////
@@ -142,34 +132,33 @@ void menu_item_gcode::action(PGM_P pgcode) { enqueue_and_echo_commands_P(pgcode)
  * ...which calls:
  *       menu_item_int3::action_setting_edit(PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
  */
-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;
+void MenuItemBase::edit(strfunc_t strfunc, loadfunc_t loadfunc) {
+  ui.encoder_direction_normal();
+  if ((int32_t)ui.encoderPosition < 0) ui.encoderPosition = 0;
+  if ((int32_t)ui.encoderPosition > maxEditValue) ui.encoderPosition = maxEditValue;
+  if (ui.should_draw())
+    draw_edit_screen(editLabel, strfunc(ui.encoderPosition + minEditValue));
+  if (ui.lcd_clicked || (liveEdit && ui.should_draw())) {
+    if (editValue != NULL) loadfunc(editValue, ui.encoderPosition + minEditValue);
+    if (callbackFunc && (liveEdit || ui.lcd_clicked)) (*callbackFunc)();
+    if (ui.use_click()) ui.goto_previous_screen();
   }
 }
 
-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();
+void MenuItemBase::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) {
+  ui.save_previous_screen();
+  ui.refresh();
   editLabel = el;
   editValue = ev;
   minEditValue = minv;
   maxEditValue = maxv;
-  encoderPosition = ep;
-  currentScreen = cs;
+  ui.encoderPosition = ep;
+  ui.currentScreen = cs;
   callbackFunc = cb;
   liveEdit = le;
 }
 
-#define DEFINE_MENU_EDIT_ITEM(NAME) template class menu_item_template<NAME ## _item_info>;
+#define DEFINE_MENU_EDIT_ITEM(NAME) template class TMenuItem<NAME ## _item_info>;
 
 DEFINE_MENU_EDIT_ITEM(int3);
 DEFINE_MENU_EDIT_ITEM(int4);
@@ -184,7 +173,7 @@ 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();
+  UNUSED(pstr); *ptr ^= true; ui.refresh();
   if (callback) (*callback)();
 }
 
@@ -202,7 +191,7 @@ bool printer_busy() { return planner.movesplanned() || IS_SD_PRINTING(); }
 /**
  * General function to go directly to a screen
  */
-void lcd_goto_screen(screenFunc_t screen, const uint32_t encoder/*=0*/) {
+void MarlinUI::goto_screen(screenFunc_t screen, const uint32_t encoder/*=0*/) {
   if (currentScreen != screen) {
 
     #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
@@ -215,10 +204,10 @@ void lcd_goto_screen(screenFunc_t screen, const uint32_t encoder/*=0*/) {
       // Going to menu_main from status screen? Remember first click time.
       // Going back to status screen within a very short time? Go to Z babystepping.
       if (screen == menu_main) {
-        if (currentScreen == lcd_status_screen)
+        if (on_status_screen())
           doubleclick_expire_ms = millis() + DOUBLECLICK_MAX_INTERVAL;
       }
-      else if (screen == lcd_status_screen && currentScreen == menu_main && PENDING(millis(), doubleclick_expire_ms)) {
+      else if (screen == status_screen && currentScreen == menu_main && PENDING(millis(), doubleclick_expire_ms)) {
         if (printer_busy()) {
           screen =
             #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
@@ -239,25 +228,25 @@ void lcd_goto_screen(screenFunc_t screen, const uint32_t encoder/*=0*/) {
 
     currentScreen = screen;
     encoderPosition = encoder;
-    if (screen == lcd_status_screen) {
-      set_defer_return_to_status(false);
+    if (screen == status_screen) {
+      ui.defer_status_screen(false);
       #if ENABLED(AUTO_BED_LEVELING_UBL)
         ubl.lcd_map_control = false;
       #endif
       screen_history_depth = 0;
     }
 
-    lcd_implementation_clear();
+    clear_lcd();
 
     // Re-initialize custom characters that may be re-used
     #if HAS_CHARACTER_LCD
       #if ENABLED(AUTO_BED_LEVELING_UBL)
         if (!ubl.lcd_map_control)
       #endif
-          LCD_SET_CHARSET(screen == lcd_status_screen ? CHARSET_INFO : CHARSET_MENU);
+          LCD_SET_CHARSET(screen == status_screen ? CHARSET_INFO : CHARSET_MENU);
     #endif
 
-    lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+    refresh(LCDVIEW_CALL_REDRAW_NEXT);
     screen_changed = true;
     #if HAS_GRAPHICAL_LCD
       drawing_screen = false;
@@ -276,24 +265,24 @@ void lcd_goto_screen(screenFunc_t screen, const uint32_t encoder/*=0*/) {
 //
 static PGM_P sync_message;
 
-void _lcd_synchronize() {
-  if (lcdDrawUpdate) lcd_implementation_drawmenu_static(LCD_HEIGHT >= 4 ? 1 : 0, sync_message);
+void MarlinUI::_synchronize() {
+  if (should_draw()) draw_menu_item_static(LCD_HEIGHT >= 4 ? 1 : 0, sync_message);
   if (no_reentry) return;
   // Make this the current handler till all moves are done
   no_reentry = true;
   const screenFunc_t old_screen = currentScreen;
-  lcd_goto_screen(_lcd_synchronize);
+  goto_screen(_synchronize);
   planner.synchronize(); // idle() is called until moves complete
   no_reentry = false;
-  lcd_goto_screen(old_screen);
+  goto_screen(old_screen);
 }
 
 // Display the synchronize screen with a custom message
 // ** This blocks the command queue! **
-void lcd_synchronize(PGM_P const msg/*=NULL*/) {
+void MarlinUI::synchronize(PGM_P const msg/*=NULL*/) {
   static const char moving[] PROGMEM = MSG_MOVING;
   sync_message = msg ? msg : moving;
-  _lcd_synchronize();
+  _synchronize();
 }
 
 /**
@@ -308,16 +297,16 @@ void lcd_synchronize(PGM_P const msg/*=NULL*/) {
  */
 int8_t encoderLine, screen_items;
 void scroll_screen(const uint8_t limit, const bool is_menu) {
-  ENCODER_DIRECTION_MENUS();
+  ui.encoder_direction_menus();
   ENCODER_RATE_MULTIPLY(false);
-  if (encoderPosition > 0x8000) encoderPosition = 0;
-  if (first_page) {
-    encoderLine = encoderPosition / (ENCODER_STEPS_PER_MENU_ITEM);
+  if (ui.encoderPosition > 0x8000) ui.encoderPosition = 0;
+  if (ui.first_page) {
+    encoderLine = ui.encoderPosition / (ENCODER_STEPS_PER_MENU_ITEM);
     screen_changed = false;
   }
   if (screen_items > 0 && encoderLine >= screen_items - limit) {
     encoderLine = MAX(0, screen_items - limit);
-    encoderPosition = encoderLine * (ENCODER_STEPS_PER_MENU_ITEM);
+    ui.encoderPosition = encoderLine * (ENCODER_STEPS_PER_MENU_ITEM);
   }
   if (is_menu) {
     NOMORE(encoderTopLine, encoderLine);
@@ -328,12 +317,12 @@ void scroll_screen(const uint8_t limit, const bool is_menu) {
     encoderTopLine = encoderLine;
 }
 
-void lcd_completion_feedback(const bool good/*=true*/) {
+void MarlinUI::completion_feedback(const bool good/*=true*/) {
   if (good) {
-    lcd_buzz(100, 659);
-    lcd_buzz(100, 698);
+    BUZZ(100, 659);
+    BUZZ(100, 698);
   }
-  else lcd_buzz(20, 440);
+  else BUZZ(20, 440);
 }
 
 #if HAS_LINE_TO_Z
@@ -348,17 +337,17 @@ void lcd_completion_feedback(const bool good/*=true*/) {
 #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
 
   void lcd_babystep_zoffset() {
-    if (use_click()) { return lcd_goto_previous_menu_no_defer(); }
-    set_defer_return_to_status(true);
+    if (ui.use_click()) return ui.goto_previous_screen_no_defer();
+    ui.defer_status_screen(true);
     #if ENABLED(BABYSTEP_HOTEND_Z_OFFSET)
       const bool do_probe = (active_extruder == 0);
     #else
       constexpr bool do_probe = true;
     #endif
-    ENCODER_DIRECTION_NORMAL();
-    if (encoderPosition) {
-      const int16_t babystep_increment = (int32_t)encoderPosition * (BABYSTEP_MULTIPLICATOR);
-      encoderPosition = 0;
+    ui.encoder_direction_normal();
+    if (ui.encoderPosition) {
+      const int16_t babystep_increment = (int32_t)ui.encoderPosition * (BABYSTEP_MULTIPLICATOR);
+      ui.encoderPosition = 0;
 
       const float diff = planner.steps_to_mm[Z_AXIS] * babystep_increment,
                   new_probe_offset = zprobe_zoffset + diff,
@@ -378,16 +367,16 @@ void lcd_completion_feedback(const bool good/*=true*/) {
           else hotend_offset[Z_AXIS][active_extruder] = new_offs;
         #endif
 
-        lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+        ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
       }
     }
-    if (lcdDrawUpdate) {
+    if (ui.should_draw()) {
       #if ENABLED(BABYSTEP_HOTEND_Z_OFFSET)
         if (!do_probe)
-          lcd_implementation_drawedit(PSTR(MSG_IDEX_Z_OFFSET), ftostr43sign(hotend_offset[Z_AXIS][active_extruder]));
+          draw_edit_screen(PSTR(MSG_IDEX_Z_OFFSET), ftostr43sign(hotend_offset[Z_AXIS][active_extruder]));
         else
       #endif
-          lcd_implementation_drawedit(PSTR(MSG_ZPROBE_ZOFFSET), ftostr43sign(zprobe_zoffset));
+          draw_edit_screen(PSTR(MSG_ZPROBE_ZOFFSET), ftostr43sign(zprobe_zoffset));
 
       #if ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY)
         if (do_probe) _lcd_zoffset_overlay_gfx(zprobe_zoffset);
@@ -447,14 +436,14 @@ void watch_temp_callback_bed() {
 #endif
 
 #if ENABLED(EEPROM_SETTINGS)
-  void lcd_store_settings()   { lcd_completion_feedback(settings.save()); }
-  void lcd_load_settings()    { lcd_completion_feedback(settings.load()); }
+  void lcd_store_settings()   { ui.completion_feedback(settings.save()); }
+  void lcd_load_settings()    { ui.completion_feedback(settings.load()); }
 #endif
 
 void _lcd_draw_homing() {
   constexpr uint8_t line = (LCD_HEIGHT - 1) / 2;
-  if (lcdDrawUpdate) lcd_implementation_drawmenu_static(line, PSTR(MSG_LEVEL_BED_HOMING));
-  lcdDrawUpdate = LCDVIEW_CALL_NO_REDRAW;
+  if (ui.should_draw()) draw_menu_item_static(line, PSTR(MSG_LEVEL_BED_HOMING));
+  ui.refresh(LCDVIEW_CALL_NO_REDRAW);
 }
 
 #if ENABLED(LCD_BED_LEVELING) || (HAS_LEVELING && DISABLED(SLIM_LCD_MENUS))
diff --git a/Marlin/src/lcd/menu/menu.h b/Marlin/src/lcd/menu/menu.h
index 2dffc7e1c1..d54e0a831d 100644
--- a/Marlin/src/lcd/menu/menu.h
+++ b/Marlin/src/lcd/menu/menu.h
@@ -31,14 +31,6 @@ constexpr int16_t heater_maxtemp[HOTENDS] = ARRAY_BY_HOTENDS(HEATER_0_MAXTEMP, H
 
 void scroll_screen(const uint8_t limit, const bool is_menu);
 bool printer_busy();
-void lcd_completion_feedback(const bool good=true);
-void lcd_save_previous_screen();
-void lcd_goto_previous_menu();
-#if LCD_TIMEOUT_TO_STATUS
-  void lcd_goto_previous_menu_no_defer();
-#else
-  #define lcd_goto_previous_menu_no_defer() lcd_goto_previous_menu()
-#endif
 
 ////////////////////////////////////////////
 ////////// Menu Item Numeric Types /////////
@@ -67,60 +59,46 @@ DECLARE_MENU_EDIT_TYPE(uint32_t, long5, ftostr5rj, 0.01f);
 ///////// Menu Item Draw Functions /////////
 ////////////////////////////////////////////
 
+void draw_menu_item_generic(const bool isSelected, const uint8_t row, PGM_P const pstr, const char pre_char, const char post_char);
+void draw_menu_item_static(const uint8_t row, PGM_P const pstr, const bool center=true, const bool invert=false, const char *valstr=NULL);
+void draw_edit_screen(PGM_P const pstr, const char* const value=NULL);
 #if ENABLED(SDSUPPORT)
   class CardReader;
-#endif
-
-void lcd_implementation_drawmenu_generic(const bool isSelected, const uint8_t row, const char* pstr, const char pre_char, const char post_char);
-void lcd_implementation_drawmenu_static(const uint8_t row, const char* pstr, const bool center=true, const bool invert=false, const char *valstr=NULL);
-void lcd_implementation_drawedit(const char* const pstr, const char* const value=NULL);
-#if ENABLED(ADVANCED_PAUSE_FEATURE)
-  void lcd_implementation_hotend_status(const uint8_t row, const uint8_t extruder);
+  void draw_sd_menu_item(const bool isSelected, const uint8_t row, PGM_P const pstr, CardReader &theCard, const bool isDir);
+  inline void draw_menu_item_sdfile(const bool sel, const uint8_t row, PGM_P const pstr, CardReader &theCard) { draw_sd_menu_item(sel, row, pstr, theCard, false); }
+  inline void draw_menu_item_sdfolder(const bool sel, const uint8_t row, PGM_P const pstr, CardReader &theCard) { draw_sd_menu_item(sel, row, pstr, theCard, true); }
 #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) 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)
-  #define DRAW_BOOL_SETTING(sel, row, pstr, data) lcd_implementation_drawmenu_setting_edit_generic_P(sel, row, pstr, (*(data))?PSTR(MSG_ON):PSTR(MSG_OFF))
-  #if ENABLED(SDSUPPORT)
-    void _drawmenu_sd(const bool isSelected, const uint8_t row, PGM_P const pstr, CardReader &theCard, const bool isDir);
-    #define lcd_implementation_drawmenu_sdfile(sel, row, pstr, theCard) _drawmenu_sd(sel, row, pstr, theCard, false)
-    #define lcd_implementation_drawmenu_sddirectory(sel, row, pstr, theCard) _drawmenu_sd(sel, row, pstr, theCard, true)
-  #endif
+  #define draw_menu_item_back(sel, row, pstr) draw_menu_item_generic(sel, row, pstr, LCD_STR_UPLEVEL[0], LCD_STR_UPLEVEL[0])
+  #define draw_menu_item_setting_edit_generic(sel, row, pstr, data) _drawmenu_setting_edit_generic(sel, row, pstr, data, false)
+  #define draw_menu_item_setting_edit_generic_P(sel, row, pstr, data) _drawmenu_setting_edit_generic(sel, row, pstr, data, true)
+  #define DRAWMENU_SETTING_EDIT_GENERIC(SRC) draw_menu_item_setting_edit_generic(sel, row, pstr, SRC)
+  #define DRAW_BOOL_SETTING(sel, row, pstr, data) draw_menu_item_setting_edit_generic_P(sel, row, pstr, (*(data))?PSTR(MSG_ON):PSTR(MSG_OFF))
   #if ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY) || ENABLED(MESH_EDIT_GFX_OVERLAY)
     void _lcd_zoffset_overlay_gfx(const float zvalue);
   #endif
 #else
-  #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)
-  #define DRAW_BOOL_SETTING(sel, row, pstr, data) lcd_implementation_drawmenu_setting_edit_generic_P(sel, row, pstr, '>', (*(data))?PSTR(MSG_ON):PSTR(MSG_OFF))
-  #if ENABLED(SDSUPPORT)
-    void lcd_implementation_drawmenu_sdfile(const bool sel, const uint8_t row, PGM_P pstr, CardReader &theCard);
-    void lcd_implementation_drawmenu_sddirectory(const bool sel, const uint8_t row, PGM_P pstr, CardReader &theCard);
-  #endif
-#endif
-#define lcd_implementation_drawmenu_submenu(sel, row, pstr, data) lcd_implementation_drawmenu_generic(sel, row, pstr, '>', LCD_STR_ARROW_RIGHT[0])
-#define lcd_implementation_drawmenu_gcode(sel, row, pstr, gcode) lcd_implementation_drawmenu_generic(sel, row, pstr, '>', ' ')
-#define lcd_implementation_drawmenu_function(sel, row, pstr, data) lcd_implementation_drawmenu_generic(sel, row, pstr, '>', ' ')
-
-#if ENABLED(AUTO_BED_LEVELING_UBL)
-  void lcd_implementation_ubl_plot(const uint8_t x, const uint8_t inverted_y);
+  #define draw_menu_item_back(sel, row, pstr) draw_menu_item_generic(sel, row, pstr, LCD_UPLEVEL_CHAR, LCD_UPLEVEL_CHAR)
+  void draw_menu_item_setting_edit_generic(const bool sel, const uint8_t row, const char* pstr, const char pre_char, const char* const data);
+  void draw_menu_item_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) draw_menu_item_setting_edit_generic(sel, row, pstr, '>', SRC)
+  #define DRAW_BOOL_SETTING(sel, row, pstr, data) draw_menu_item_setting_edit_generic_P(sel, row, pstr, '>', (*(data))?PSTR(MSG_ON):PSTR(MSG_OFF))
 #endif
+#define draw_menu_item_submenu(sel, row, pstr, data) draw_menu_item_generic(sel, row, pstr, '>', LCD_STR_ARROW_RIGHT[0])
+#define draw_menu_item_gcode(sel, row, pstr, gcode) draw_menu_item_generic(sel, row, pstr, '>', ' ')
+#define draw_menu_item_function(sel, row, pstr, data) draw_menu_item_generic(sel, row, pstr, '>', ' ')
 
 ////////////////////////////////////////////
 /////// Edit Setting Draw Functions ////////
 ////////////////////////////////////////////
 
 #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, ...) { \
+  FORCE_INLINE void draw_menu_item_setting_edit_ ## NAME (const bool sel, const uint8_t row, PGM_P const pstr, PGM_P const 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), ...) { \
+  FORCE_INLINE void draw_menu_item_setting_edit_accessor_ ## NAME (const bool sel, const uint8_t row, PGM_P const pstr, PGM_P const pstr2, TYPE (*pget)(), void (*pset)(TYPE), ...) { \
     UNUSED(pstr2); UNUSED(pset); \
     DRAWMENU_SETTING_EDIT_GENERIC(STRFUNC(pget())); \
   } \
@@ -139,8 +117,8 @@ 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)
+#define draw_menu_item_setting_edit_bool(sel, row, pstr, pstr2, data, ...)           DRAW_BOOL_SETTING(sel, row, pstr, data)
+#define draw_menu_item_setting_edit_accessor_bool(sel, row, pstr, pstr2, pget, pset) DRAW_BOOL_SETTING(sel, row, pstr, data)
 
 ////////////////////////////////////////////
 /////////////// Menu Actions ///////////////
@@ -148,12 +126,12 @@ DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(long5);
 
 class menu_item_back {
   public:
-    static inline void action() { lcd_goto_previous_menu(); }
+    static inline void action() { ui.goto_previous_screen(); }
 };
 
 class menu_item_submenu {
   public:
-    static inline void action(const screenFunc_t func) { lcd_save_previous_screen(); lcd_goto_screen(func); }
+    static inline void action(const screenFunc_t func) { ui.save_previous_screen(); ui.goto_screen(func); }
 };
 
 class menu_item_gcode {
@@ -170,7 +148,7 @@ class menu_item_function {
 /////////// Menu Editing Actions ///////////
 ////////////////////////////////////////////
 
-class menu_item_invariants {
+class MenuItemBase {
   protected:
     typedef char* (*strfunc_t)(const int32_t);
     typedef void (*loadfunc_t)(void *, const int32_t);
@@ -179,7 +157,7 @@ class menu_item_invariants {
 };
 
 template<typename NAME>
-class menu_item_template : menu_item_invariants {
+class TMenuItem : MenuItemBase {
   private:
     typedef typename NAME::type_t type_t;
     inline static float unscale(const float value)    {return value * (1.0f / NAME::scale);}
@@ -191,10 +169,10 @@ class menu_item_template : menu_item_invariants {
       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);}
+    static void edit() { MenuItemBase::edit(to_string, load); }
 };
 
-#define DECLARE_MENU_EDIT_ITEM(NAME) typedef menu_item_template<NAME ## _item_info> menu_item_ ## NAME;
+#define DECLARE_MENU_EDIT_ITEM(NAME) typedef TMenuItem<NAME ## _item_info> menu_item_ ## NAME;
 
 DECLARE_MENU_EDIT_ITEM(int3);
 DECLARE_MENU_EDIT_ITEM(int4);
@@ -210,7 +188,7 @@ 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);
+    static void action_setting_edit(PGM_P const pstr, bool* ptr, const screenFunc_t callbackFunc=NULL);
 };
 
 ////////////////////////////////////////////
@@ -256,69 +234,47 @@ class menu_item_bool {
   screen_items = _thisItemNr; \
   UNUSED(_skipStatic)
 
-/**
- * REVERSE_MENU_DIRECTION
- *
- * To reverse the menu direction we need a general way to reverse
- * the direction of the encoder everywhere. So encoderDirection is
- * added to allow the encoder to go the other way.
- *
- * This behavior is limited to scrolling Menus and SD card listings,
- * and is disabled in other contexts.
- */
-#if ENABLED(REVERSE_MENU_DIRECTION)
-  extern int8_t encoderDirection;
-  #define ENCODER_DIRECTION_NORMAL() (encoderDirection =  1)
-  #define ENCODER_DIRECTION_MENUS()  (encoderDirection = -1)
-#else
-  #define ENCODER_DIRECTION_NORMAL() NOOP
-  #define ENCODER_DIRECTION_MENUS()  NOOP
-#endif
-
 #if ENABLED(ENCODER_RATE_MULTIPLIER)
-  extern millis_t lastEncoderMovementMillis;
-  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_MULTIPLY(F) (ui.encoderRateMultiplierEnabled = F)
+  #define _MENU_ITEM_MULTIPLIER_CHECK(USE_MULTIPLIER) do{ if (USE_MULTIPLIER) ui.enable_encoder_multiplier(true); }while(0)
   //#define ENCODER_RATE_MULTIPLIER_DEBUG  // If defined, output the encoder steps per second value
-#else // !ENCODER_RATE_MULTIPLIER
+#else
   #define ENCODER_RATE_MULTIPLY(F) NOOP
   #define _MENU_ITEM_MULTIPLIER_CHECK(USE_MULTIPLIER)
-#endif // !ENCODER_RATE_MULTIPLIER
+#endif
 
 /**
  * MENU_ITEM generates draw & handler code for a menu item, potentially calling:
  *
- *   lcd_implementation_drawmenu_<type>[_variant](sel, row, label, arg3...)
+ *   draw_menu_item_<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))
+ *     draw_menu_item_back(sel, row, PSTR(MSG_WATCH))
  *     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)
+ *     draw_menu_item_function(sel, row, PSTR(MSG_PAUSE_PRINT), lcd_sdcard_pause)
  *     menu_item_function::action(lcd_sdcard_pause)
  *
  *   MENU_ITEM_EDIT(int3, MSG_SPEED, &feedrate_percentage, 10, 999)
- *     lcd_implementation_drawmenu_setting_edit_int3(sel, row, PSTR(MSG_SPEED), PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
+ *     draw_menu_item_setting_edit_int3(sel, row, PSTR(MSG_SPEED), PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
  *     menu_item_int3::action_setting_edit(PSTR(MSG_SPEED), &feedrate_percentage, 10, 999)
  *
  */
 #define _MENU_ITEM_VARIANT_P(TYPE, VARIANT, USE_MULTIPLIER, PLABEL, ...) do { \
     _skipStatic = false; \
     if (_menuLineNr == _thisItemNr) { \
-      if (encoderLine == _thisItemNr && lcd_clicked) { \
-        lcd_clicked = false; \
+      if (encoderLine == _thisItemNr && ui.use_click()) { \
         _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__); \
+      if (ui.should_draw()) \
+        draw_menu_item ## VARIANT ## _ ## TYPE(encoderLine == _thisItemNr, _lcdLineNr, PLABEL, ## __VA_ARGS__); \
     } \
   ++_thisItemNr; \
 }while(0)
@@ -328,17 +284,17 @@ class menu_item_bool {
 #define STATIC_ITEM_P(PLABEL, ...) do{ \
   if (_menuLineNr == _thisItemNr) { \
     if (_skipStatic && encoderLine <= _thisItemNr) { \
-      encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \
+      ui.encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \
       ++encoderLine; \
     } \
-    if (lcdDrawUpdate) \
-      lcd_implementation_drawmenu_static(_lcdLineNr, PLABEL, ## __VA_ARGS__); \
+    if (ui.should_draw()) \
+      draw_menu_item_static(_lcdLineNr, PLABEL, ## __VA_ARGS__); \
   } \
   ++_thisItemNr; \
 } while(0)
 
 #define MENU_ITEM_ADDON_START(X) \
-  if (lcdDrawUpdate && _menuLineNr == _thisItemNr - 1) { \
+  if (ui.should_draw() && _menuLineNr == _thisItemNr - 1) { \
     SETCURSOR(X, _lcdLineNr)
 
 #define MENU_ITEM_ADDON_END() } (0)
@@ -347,12 +303,12 @@ class menu_item_bool {
 
 #define MENU_BACK(LABEL) MENU_ITEM(back, LABEL)
 #define MENU_ITEM_DUMMY() do { _thisItemNr++; }while(0)
-#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__)
+#define MENU_ITEM_P(TYPE, PLABEL, ...)                       _MENU_ITEM_VARIANT_P(TYPE,              , false, PLABEL,                   ## __VA_ARGS__)
+#define MENU_ITEM(TYPE, LABEL, ...)                          _MENU_ITEM_VARIANT_P(TYPE,              , false, PSTR(LABEL),              ## __VA_ARGS__)
+#define MENU_ITEM_EDIT(TYPE, LABEL, ...)                     _MENU_ITEM_VARIANT_P(TYPE, _setting_edit, false, PSTR(LABEL), PSTR(LABEL), ## __VA_ARGS__)
+#define MENU_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...)            _MENU_ITEM_VARIANT_P(TYPE, _setting_edit, false, PSTR(LABEL), PSTR(LABEL), ## __VA_ARGS__)
+#define MENU_MULTIPLIER_ITEM_EDIT(TYPE, LABEL, ...)          _MENU_ITEM_VARIANT_P(TYPE, _setting_edit,  true, PSTR(LABEL), PSTR(LABEL), ## __VA_ARGS__)
+#define MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...) _MENU_ITEM_VARIANT_P(TYPE, _setting_edit,  true, PSTR(LABEL), PSTR(LABEL), ## __VA_ARGS__)
 
 ////////////////////////////////////////////
 /////////////// Menu Screens ///////////////
@@ -379,7 +335,6 @@ void menu_move();
 ////////////////////////////////////////////
 
 void lcd_move_z();
-void lcd_synchronize(PGM_P const msg=NULL);
 void _lcd_draw_homing();
 
 void watch_temp_callback_E0();
@@ -426,3 +381,7 @@ void watch_temp_callback_bed();
   void lcd_store_settings();
   void lcd_load_settings();
 #endif
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+  void menu_job_recovery();
+#endif
diff --git a/Marlin/src/lcd/menu/menu_advanced.cpp b/Marlin/src/lcd/menu/menu_advanced.cpp
index a6174d5a70..bebf749214 100644
--- a/Marlin/src/lcd/menu/menu_advanced.cpp
+++ b/Marlin/src/lcd/menu/menu_advanced.cpp
@@ -52,7 +52,7 @@
   //
   void _lcd_set_home_offsets() {
     enqueue_and_echo_commands_P(PSTR("M428"));
-    lcd_return_to_status();
+    ui.return_to_status();
   }
 #endif
 
@@ -65,9 +65,9 @@
   //
   static void _lcd_toggle_sd_update() {
     const bool new_state = !settings.sd_update_status();
-    lcd_completion_feedback(settings.set_sd_update_status(new_state));
-    lcd_return_to_status();
-    if (new_state) LCD_MESSAGEPGM(MSG_RESET_PRINTER); else lcd_reset_status();
+    ui.completion_feedback(settings.set_sd_update_status(new_state));
+    ui.return_to_status();
+    if (new_state) LCD_MESSAGEPGM(MSG_RESET_PRINTER); else ui.reset_status();
   }
 #endif
 
@@ -539,8 +539,8 @@ void menu_advanced_temperature() {
     #include "../../module/configuration_store.h"
 
     static void lcd_init_eeprom() {
-      lcd_completion_feedback(settings.init_eeprom());
-      lcd_goto_previous_menu();
+      ui.completion_feedback(settings.init_eeprom());
+      ui.goto_previous_screen();
     }
 
     static void lcd_init_eeprom_confirm() {
diff --git a/Marlin/src/lcd/menu/menu_bed_corners.cpp b/Marlin/src/lcd/menu/menu_bed_corners.cpp
index 0d51ef346d..037d6be03a 100644
--- a/Marlin/src/lcd/menu/menu_bed_corners.cpp
+++ b/Marlin/src/lcd/menu/menu_bed_corners.cpp
@@ -77,7 +77,7 @@ void menu_level_bed_corners() {
       MSG_NEXT_CORNER
     #endif
     , _lcd_goto_next_corner);
-  MENU_ITEM(function, MSG_BACK, lcd_goto_previous_menu_no_defer);
+  MENU_ITEM(function, MSG_BACK, ui.goto_previous_screen_no_defer);
   END_MENU();
 }
 
@@ -85,18 +85,18 @@ void _lcd_level_bed_corners_homing() {
   _lcd_draw_homing();
   if (all_axes_homed()) {
     bed_corner = 0;
-    lcd_goto_screen(menu_level_bed_corners);
+    ui.goto_screen(menu_level_bed_corners);
     _lcd_goto_next_corner();
   }
 }
 
 void _lcd_level_bed_corners() {
-  set_defer_return_to_status(true);
+  ui.defer_status_screen(true);
   if (!all_axes_known()) {
     set_all_unhomed();
     enqueue_and_echo_commands_P(PSTR("G28"));
   }
-  lcd_goto_screen(_lcd_level_bed_corners_homing);
+  ui.goto_screen(_lcd_level_bed_corners_homing);
 }
 
 #endif // HAS_LCD_MENU && LEVEL_BED_CORNERS
diff --git a/Marlin/src/lcd/menu/menu_bed_leveling.cpp b/Marlin/src/lcd/menu/menu_bed_leveling.cpp
index ae43ca12b4..40c51b5726 100644
--- a/Marlin/src/lcd/menu/menu_bed_leveling.cpp
+++ b/Marlin/src/lcd/menu/menu_bed_leveling.cpp
@@ -26,7 +26,7 @@
 
 #include "../../inc/MarlinConfigPre.h"
 
-#if HAS_LCD_MENU && ENABLED(LCD_BED_LEVELING)
+#if ENABLED(LCD_BED_LEVELING)
 
 #include "menu.h"
 #include "../../module/planner.h"
@@ -56,7 +56,7 @@
     #endif
   );
 
-  bool lcd_wait_for_move;
+  bool MarlinUI::wait_for_bl_move; // = false
 
   //
   // Bed leveling is done. Wait for G29 to complete.
@@ -70,17 +70,17 @@
   // ** This blocks the command queue! **
   //
   void _lcd_level_bed_done() {
-    if (!lcd_wait_for_move) {
+    if (!ui.wait_for_bl_move) {
       #if MANUAL_PROBE_HEIGHT > 0 && DISABLED(MESH_BED_LEVELING)
         // Display "Done" screen and wait for moves to complete
         line_to_z(MANUAL_PROBE_HEIGHT);
-        lcd_synchronize(PSTR(MSG_LEVEL_BED_DONE));
+        ui.synchronize(PSTR(MSG_LEVEL_BED_DONE));
       #endif
-      lcd_goto_previous_menu_no_defer();
-      lcd_completion_feedback();
+      ui.goto_previous_screen_no_defer();
+      ui.completion_feedback();
     }
-    if (lcdDrawUpdate) lcd_implementation_drawmenu_static(LCD_HEIGHT >= 4 ? 1 : 0, PSTR(MSG_LEVEL_BED_DONE));
-    lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+    if (ui.should_draw()) draw_menu_item_static(LCD_HEIGHT >= 4 ? 1 : 0, PSTR(MSG_LEVEL_BED_DONE));
+    ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
   }
 
   void _lcd_level_goto_next_point();
@@ -89,9 +89,9 @@
   // Step 7: Get the Z coordinate, click goes to the next point or exits
   //
   void _lcd_level_bed_get_z() {
-    ENCODER_DIRECTION_NORMAL();
+    ui.encoder_direction_normal();
 
-    if (use_click()) {
+    if (ui.use_click()) {
 
       //
       // Save the current Z position and move
@@ -102,8 +102,8 @@
         //
         // The last G29 records the point and enables bed leveling
         //
-        lcd_wait_for_move = true;
-        lcd_goto_screen(_lcd_level_bed_done);
+        ui.wait_for_bl_move = true;
+        ui.goto_screen(_lcd_level_bed_done);
         #if ENABLED(MESH_BED_LEVELING)
           enqueue_and_echo_commands_P(PSTR("G29 S2"));
         #elif ENABLED(PROBE_MANUALLY)
@@ -119,19 +119,19 @@
     //
     // Encoder knob or keypad buttons adjust the Z position
     //
-    if (encoderPosition) {
-      const float z = current_position[Z_AXIS] + float((int32_t)encoderPosition) * (MESH_EDIT_Z_STEP);
+    if (ui.encoderPosition) {
+      const float z = current_position[Z_AXIS] + float((int32_t)ui.encoderPosition) * (MESH_EDIT_Z_STEP);
       line_to_z(constrain(z, -(LCD_PROBE_Z_RANGE) * 0.5f, (LCD_PROBE_Z_RANGE) * 0.5f));
-      lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
-      encoderPosition = 0;
+      ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
+      ui.encoderPosition = 0;
     }
 
     //
     // Draw on first display, then only on Z change
     //
-    if (lcdDrawUpdate) {
+    if (ui.should_draw()) {
       const float v = current_position[Z_AXIS];
-      lcd_implementation_drawedit(PSTR(MSG_MOVE_Z), ftostr43sign(v + (v < 0 ? -0.0001f : 0.0001f), '+'));
+      draw_edit_screen(PSTR(MSG_MOVE_Z), ftostr43sign(v + (v < 0 ? -0.0001f : 0.0001f), '+'));
     }
   }
 
@@ -139,23 +139,23 @@
   // Step 6: Display "Next point: 1 / 9" while waiting for move to finish
   //
   void _lcd_level_bed_moving() {
-    if (lcdDrawUpdate) {
+    if (ui.should_draw()) {
       char msg[10];
       sprintf_P(msg, PSTR("%i / %u"), (int)(manual_probe_index + 1), total_probe_points);
-      lcd_implementation_drawedit(PSTR(MSG_LEVEL_BED_NEXT_POINT), msg);
+      draw_edit_screen(PSTR(MSG_LEVEL_BED_NEXT_POINT), msg);
     }
-    lcdDrawUpdate = LCDVIEW_CALL_NO_REDRAW;
-    if (!lcd_wait_for_move) lcd_goto_screen(_lcd_level_bed_get_z);
+    ui.refresh(LCDVIEW_CALL_NO_REDRAW);
+    if (!ui.wait_for_bl_move) ui.goto_screen(_lcd_level_bed_get_z);
   }
 
   //
   // Step 5: Initiate a move to the next point
   //
   void _lcd_level_goto_next_point() {
-    lcd_goto_screen(_lcd_level_bed_moving);
+    ui.goto_screen(_lcd_level_bed_moving);
 
     // G29 Records Z, moves, and signals when it pauses
-    lcd_wait_for_move = true;
+    ui.wait_for_bl_move = true;
     #if ENABLED(MESH_BED_LEVELING)
       enqueue_and_echo_commands_P(manual_probe_index ? PSTR("G29 S2") : PSTR("G29 S1"));
     #elif ENABLED(PROBE_MANUALLY)
@@ -168,8 +168,8 @@
   //         Move to the first probe position
   //
   void _lcd_level_bed_homing_done() {
-    if (lcdDrawUpdate) lcd_implementation_drawedit(PSTR(MSG_LEVEL_BED_WAITING));
-    if (use_click()) {
+    if (ui.should_draw()) draw_edit_screen(PSTR(MSG_LEVEL_BED_WAITING));
+    if (ui.use_click()) {
       manual_probe_index = 0;
       _lcd_level_goto_next_point();
     }
@@ -180,7 +180,7 @@
   //
   void _lcd_level_bed_homing() {
     _lcd_draw_homing();
-    if (all_axes_homed()) lcd_goto_screen(_lcd_level_bed_homing_done);
+    if (all_axes_homed()) ui.goto_screen(_lcd_level_bed_homing_done);
   }
 
   #if ENABLED(PROBE_MANUALLY)
@@ -191,9 +191,9 @@
   // Step 2: Continue Bed Leveling...
   //
   void _lcd_level_bed_continue() {
-    set_defer_return_to_status(true);
+    ui.defer_status_screen(true);
     set_all_unhomed();
-    lcd_goto_screen(_lcd_level_bed_homing);
+    ui.goto_screen(_lcd_level_bed_homing);
     enqueue_and_echo_commands_P(PSTR("G28"));
   }
 
@@ -292,4 +292,4 @@ void menu_bed_leveling() {
   END_MENU();
 }
 
-#endif // HAS_LCD_MENU && LCD_BED_LEVELING
+#endif // LCD_BED_LEVELING
diff --git a/Marlin/src/lcd/menu/menu_configuration.cpp b/Marlin/src/lcd/menu/menu_configuration.cpp
index f8fcd5c9c0..b6348519f6 100644
--- a/Marlin/src/lcd/menu/menu_configuration.cpp
+++ b/Marlin/src/lcd/menu/menu_configuration.cpp
@@ -41,35 +41,31 @@
 void menu_advanced_settings();
 void menu_delta_calibrate();
 
-#if HAS_LCD_CONTRAST
-  void lcd_callback_set_contrast() { set_lcd_contrast(lcd_contrast); }
-#endif
-
 static void lcd_factory_settings() {
   settings.reset();
-  lcd_completion_feedback();
+  ui.completion_feedback();
 }
 
 #if ENABLED(LCD_PROGRESS_BAR_TEST)
 
   static void progress_bar_test() {
     static int8_t bar_percent = 0;
-    if (use_click()) {
-      lcd_goto_previous_menu();
+    if (ui.use_click()) {
+      ui.goto_previous_screen();
       LCD_SET_CHARSET(CHARSET_MENU);
       return;
     }
-    bar_percent += (int8_t)encoderPosition;
+    bar_percent += (int8_t)ui.encoderPosition;
     bar_percent = constrain(bar_percent, 0, 100);
-    encoderPosition = 0;
-    lcd_implementation_drawmenu_static(0, PSTR(MSG_PROGRESS_BAR_TEST), true, true);
+    ui.encoderPosition = 0;
+    draw_menu_item_static(0, PSTR(MSG_PROGRESS_BAR_TEST), true, true);
     lcd_moveto((LCD_WIDTH) / 2 - 2, LCD_HEIGHT - 2);
     lcd_put_u8str(int(bar_percent)); lcd_put_wchar('%');
     lcd_moveto(0, LCD_HEIGHT - 1); lcd_draw_progress_bar(bar_percent);
   }
 
   void _progress_bar_test() {
-    lcd_goto_screen(progress_bar_test);
+    ui.goto_screen(progress_bar_test);
     LCD_SET_CHARSET(CHARSET_INFO);
   }
 
@@ -271,12 +267,12 @@ static void lcd_factory_settings() {
     #endif
     START_MENU();
     MENU_BACK(MSG_CONFIGURATION);
-    MENU_ITEM_EDIT(int8, MSG_FAN_SPEED, &lcd_preheat_fan_speed[material], 0, 255);
+    MENU_ITEM_EDIT(int8, MSG_FAN_SPEED, &ui.preheat_fan_speed[material], 0, 255);
     #if HAS_TEMP_HOTEND
-      MENU_ITEM_EDIT(int3, MSG_NOZZLE, &lcd_preheat_hotend_temp[material], MINTEMP_ALL, MAXTEMP_ALL - 15);
+      MENU_ITEM_EDIT(int3, MSG_NOZZLE, &ui.preheat_hotend_temp[material], MINTEMP_ALL, MAXTEMP_ALL - 15);
     #endif
     #if HAS_HEATED_BED
-      MENU_ITEM_EDIT(int3, MSG_BED, &lcd_preheat_bed_temp[material], BED_MINTEMP, BED_MAXTEMP - 15);
+      MENU_ITEM_EDIT(int3, MSG_BED, &ui.preheat_bed_temp[material], BED_MINTEMP, BED_MAXTEMP - 15);
     #endif
     #if ENABLED(EEPROM_SETTINGS)
       MENU_ITEM(function, MSG_STORE_EEPROM, lcd_store_settings);
@@ -338,7 +334,7 @@ void menu_configuration() {
   #endif
 
   #if HAS_LCD_CONTRAST
-    MENU_ITEM_EDIT_CALLBACK(int3, MSG_CONTRAST, &lcd_contrast, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX, lcd_callback_set_contrast, true);
+    MENU_ITEM_EDIT_CALLBACK(int3, MSG_CONTRAST, &ui.contrast, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX, ui.refresh_contrast, true);
   #endif
   #if ENABLED(FWRETRACT)
     MENU_ITEM(submenu, MSG_RETRACT, menu_config_retract);
diff --git a/Marlin/src/lcd/menu/menu_custom.cpp b/Marlin/src/lcd/menu/menu_custom.cpp
index f823a1049f..6b16bbc792 100644
--- a/Marlin/src/lcd/menu/menu_custom.cpp
+++ b/Marlin/src/lcd/menu/menu_custom.cpp
@@ -40,10 +40,10 @@
 void _lcd_user_gcode(PGM_P const cmd) {
   enqueue_and_echo_commands_P(cmd);
   #if ENABLED(USER_SCRIPT_AUDIBLE_FEEDBACK)
-    lcd_completion_feedback();
+    ui.completion_feedback();
   #endif
   #if ENABLED(USER_SCRIPT_RETURN)
-    lcd_return_to_status();
+    ui.return_to_status();
   #endif
 }
 
diff --git a/Marlin/src/lcd/menu/menu_delta_calibrate.cpp b/Marlin/src/lcd/menu/menu_delta_calibrate.cpp
index 231a044f44..f83057b658 100644
--- a/Marlin/src/lcd/menu/menu_delta_calibrate.cpp
+++ b/Marlin/src/lcd/menu/menu_delta_calibrate.cpp
@@ -38,9 +38,9 @@
 
 void _man_probe_pt(const float &rx, const float &ry) {
   do_blocking_move_to(rx, ry, Z_CLEARANCE_BETWEEN_PROBES);
-  lcd_synchronize();
+  ui.synchronize();
   move_menu_scale = MAX(PROBE_MANUALLY_STEP, MIN_STEPS_PER_SEGMENT / float(DEFAULT_XYZ_STEPS_PER_UNIT));
-  lcd_goto_screen(lcd_move_z);
+  ui.goto_screen(lcd_move_z);
 }
 
 #if ENABLED(DELTA_AUTO_CALIBRATION)
@@ -50,11 +50,11 @@ void _man_probe_pt(const float &rx, const float &ry) {
   float lcd_probe_pt(const float &rx, const float &ry) {
     _man_probe_pt(rx, ry);
     KEEPALIVE_STATE(PAUSED_FOR_USER);
-    set_defer_return_to_status(true);
+    ui.defer_status_screen(true);
     wait_for_user = true;
     while (wait_for_user) idle();
     KEEPALIVE_STATE(IN_HANDLER);
-    lcd_goto_previous_menu_no_defer();
+    ui.goto_previous_screen_no_defer();
     return current_position[Z_AXIS];
   }
 
@@ -66,12 +66,12 @@ void _man_probe_pt(const float &rx, const float &ry) {
 
   void _lcd_calibrate_homing() {
     _lcd_draw_homing();
-    if (all_axes_homed()) lcd_goto_previous_menu();
+    if (all_axes_homed()) ui.goto_previous_screen();
   }
 
   void _lcd_delta_calibrate_home() {
     enqueue_and_echo_commands_P(PSTR("G28"));
-    lcd_goto_screen(_lcd_calibrate_homing);
+    ui.goto_screen(_lcd_calibrate_homing);
   }
 
   void _goto_tower_x() { _man_probe_pt(cos(RADIANS(210)) * delta_calibration_radius, sin(RADIANS(210)) * delta_calibration_radius); }
diff --git a/Marlin/src/lcd/menu/menu_filament.cpp b/Marlin/src/lcd/menu/menu_filament.cpp
index 7eb5241797..b5938d0a98 100644
--- a/Marlin/src/lcd/menu/menu_filament.cpp
+++ b/Marlin/src/lcd/menu/menu_filament.cpp
@@ -323,15 +323,15 @@ static PGM_P advanced_pause_header() {
 // Portions from STATIC_ITEM...
 #define HOTEND_STATUS_ITEM() do { \
   if (_menuLineNr == _thisItemNr) { \
-    if (lcdDrawUpdate) { \
-      lcd_implementation_drawmenu_static(_lcdLineNr, PSTR(MSG_FILAMENT_CHANGE_NOZZLE), false, true); \
-      lcd_implementation_hotend_status(_lcdLineNr, hotend_status_extruder); \
+    if (ui.should_draw()) { \
+      draw_menu_item_static(_lcdLineNr, PSTR(MSG_FILAMENT_CHANGE_NOZZLE), false, true); \
+      ui.draw_hotend_status(_lcdLineNr, hotend_status_extruder); \
     } \
     if (_skipStatic && encoderLine <= _thisItemNr) { \
-      encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \
+      ui.encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \
       ++encoderLine; \
     } \
-    lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT; \
+    ui.refresh(LCDVIEW_CALL_REDRAW_NEXT); \
   } \
   ++_thisItemNr; \
 }while(0)
@@ -507,11 +507,11 @@ void lcd_advanced_pause_show_message(
   hotend_status_extruder = extruder;
   const screenFunc_t next_screen = ap_message_screen(message);
   if (next_screen) {
-    set_defer_return_to_status(true);
-    lcd_goto_screen(next_screen);
+    ui.defer_status_screen(true);
+    ui.goto_screen(next_screen);
   }
   else
-    lcd_return_to_status();
+    ui.return_to_status();
 }
 
 #endif // HAS_LCD_MENU && ADVANCED_PAUSE_FEATURE
diff --git a/Marlin/src/lcd/menu/menu_info.cpp b/Marlin/src/lcd/menu/menu_info.cpp
index a4860093eb..3bd7054cdc 100644
--- a/Marlin/src/lcd/menu/menu_info.cpp
+++ b/Marlin/src/lcd/menu/menu_info.cpp
@@ -47,7 +47,7 @@
   // About Printer > Printer Stats
   //
   void menu_info_stats() {
-    if (use_click()) { return lcd_goto_previous_menu(); }
+    if (ui.use_click()) return ui.goto_previous_screen();
 
     char buffer[21];
     printStatistics stats = print_job_timer.getStats();
@@ -80,7 +80,7 @@
 // About Printer > Thermistors
 //
 void menu_info_thermistors() {
-  if (use_click()) { return lcd_goto_previous_menu(); }
+  if (ui.use_click()) return ui.goto_previous_screen();
   START_SCREEN();
   #define THERMISTOR_ID TEMP_SENSOR_0
   #include "../thermistornames.h"
@@ -139,7 +139,7 @@ void menu_info_thermistors() {
 // About Printer > Board Info
 //
 void menu_info_board() {
-  if (use_click()) { return lcd_goto_previous_menu(); }
+  if (ui.use_click()) return ui.goto_previous_screen();
   START_SCREEN();
   STATIC_ITEM(BOARD_NAME, true, true);                           // MyPrinterController
   STATIC_ITEM(MSG_INFO_BAUDRATE ": " STRINGIFY(BAUDRATE), true); // Baud: 250000
@@ -158,7 +158,7 @@ void menu_info_board() {
 // About Printer > Printer Info
 //
 void menu_info_printer() {
-  if (use_click()) { return lcd_goto_previous_menu(); }
+  if (ui.use_click()) return ui.goto_previous_screen();
   START_SCREEN();
   STATIC_ITEM(MSG_MARLIN, true, true);                             // Marlin
   STATIC_ITEM(SHORT_BUILD_VERSION, true);                          // x.x.x-Branch
diff --git a/Marlin/src/lcd/menu/menu_job_recovery.cpp b/Marlin/src/lcd/menu/menu_job_recovery.cpp
index 31b375d0de..c0a38525d4 100644
--- a/Marlin/src/lcd/menu/menu_job_recovery.cpp
+++ b/Marlin/src/lcd/menu/menu_job_recovery.cpp
@@ -37,7 +37,7 @@ static void lcd_power_loss_recovery_resume() {
   char cmd[20];
 
   // Return to status now
-  lcd_return_to_status();
+  ui.return_to_status();
 
   // Turn leveling off and home
   enqueue_and_echo_commands_P(PSTR("M420 S0\nG28 R0"
@@ -91,11 +91,11 @@ static void lcd_power_loss_recovery_resume() {
 static void lcd_power_loss_recovery_cancel() {
   card.removeJobRecoveryFile();
   card.autostart_index = 0;
-  lcd_return_to_status();
+  ui.return_to_status();
 }
 
 void menu_job_recovery() {
-  set_defer_return_to_status(true);
+  ui.defer_status_screen(true);
   START_MENU();
   STATIC_ITEM(MSG_POWER_LOSS_RECOVERY);
   MENU_ITEM(function, MSG_RESUME_PRINT, lcd_power_loss_recovery_resume);
diff --git a/Marlin/src/lcd/menu/menu_main.cpp b/Marlin/src/lcd/menu/menu_main.cpp
index ae661a3b54..e3332d538a 100644
--- a/Marlin/src/lcd/menu/menu_main.cpp
+++ b/Marlin/src/lcd/menu/menu_main.cpp
@@ -43,7 +43,7 @@
     #if ENABLED(PARK_HEAD_ON_PAUSE)
       enqueue_and_echo_commands_P(PSTR("M125"));
     #endif
-    lcd_reset_status();
+    ui.reset_status();
   }
 
   void lcd_sdcard_resume() {
@@ -53,14 +53,14 @@
       card.startFileprint();
       print_job_timer.start();
     #endif
-    lcd_reset_status();
+    ui.reset_status();
   }
 
   void lcd_sdcard_stop() {
     wait_for_heatup = wait_for_user = false;
     card.abort_sd_printing = true;
-    lcd_setstatusPGM(PSTR(MSG_PRINT_ABORTED), -1);
-    lcd_return_to_status();
+    ui.setstatusPGM(PSTR(MSG_PRINT_ABORTED), -1);
+    ui.return_to_status();
   }
 
   #if ENABLED(MENU_ADDAUTOSTART)
diff --git a/Marlin/src/lcd/menu/menu_motion.cpp b/Marlin/src/lcd/menu/menu_motion.cpp
index e4d6f50944..e4c29ad18c 100644
--- a/Marlin/src/lcd/menu/menu_motion.cpp
+++ b/Marlin/src/lcd/menu/menu_motion.cpp
@@ -46,9 +46,6 @@
 
 extern millis_t manual_move_start_time;
 extern int8_t manual_move_axis;
-#if ENABLED(DUAL_X_CARRIAGE) || E_MANUAL > 1
-  extern int8_t manual_move_e_index;
-#endif
 #if ENABLED(MANUAL_E_MOVES_RELATIVE)
   float manual_move_e_origin = 0;
 #endif
@@ -57,18 +54,15 @@ extern int8_t manual_move_axis;
 #endif
 
 //
-// Tell lcd_update() to start a move to current_position" after a short delay.
+// Tell ui.update() to start a move to current_position" after a short delay.
 //
 inline void manual_move_to_current(AxisEnum axis
   #if E_MANUAL > 1
     , const int8_t eindex=-1
   #endif
 ) {
-  #if ENABLED(DUAL_X_CARRIAGE) || E_MANUAL > 1
-    #if E_MANUAL > 1
-      if (axis == E_AXIS)
-    #endif
-        manual_move_e_index = eindex >= 0 ? eindex : active_extruder;
+  #if E_MANUAL > 1
+    if (axis == E_AXIS) ui.manual_move_e_index = eindex >= 0 ? eindex : active_extruder;
   #endif
   manual_move_start_time = millis() + (move_menu_scale < 0.99f ? 0UL : 250UL); // delay for bigger moves
   manual_move_axis = (int8_t)axis;
@@ -79,9 +73,9 @@ inline void manual_move_to_current(AxisEnum axis
 //
 
 static void _lcd_move_xyz(PGM_P name, AxisEnum axis) {
-  if (use_click()) { return lcd_goto_previous_menu_no_defer(); }
-  ENCODER_DIRECTION_NORMAL();
-  if (encoderPosition && !processing_manual_move) {
+  if (ui.use_click()) return ui.goto_previous_screen_no_defer();
+  ui.encoder_direction_normal();
+  if (ui.encoderPosition && !ui.processing_manual_move) {
 
     // Start with no limits to movement
     float min = current_position[axis] - 1000,
@@ -127,32 +121,32 @@ static void _lcd_move_xyz(PGM_P name, AxisEnum axis) {
     #endif
 
     // Get the new position
-    const float diff = float((int32_t)encoderPosition) * move_menu_scale;
+    const float diff = float((int32_t)ui.encoderPosition) * move_menu_scale;
     #if IS_KINEMATIC
       manual_move_offset += diff;
-      if ((int32_t)encoderPosition < 0)
+      if ((int32_t)ui.encoderPosition < 0)
         NOLESS(manual_move_offset, min - current_position[axis]);
       else
         NOMORE(manual_move_offset, max - current_position[axis]);
     #else
       current_position[axis] += diff;
-      if ((int32_t)encoderPosition < 0)
+      if ((int32_t)ui.encoderPosition < 0)
         NOLESS(current_position[axis], min);
       else
         NOMORE(current_position[axis], max);
     #endif
 
     manual_move_to_current(axis);
-    lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+    ui.refresh(LCDVIEW_REDRAW_NOW);
   }
-  encoderPosition = 0;
-  if (lcdDrawUpdate) {
-    const float pos = NATIVE_TO_LOGICAL(processing_manual_move ? destination[axis] : current_position[axis]
+  ui.encoderPosition = 0;
+  if (ui.should_draw()) {
+    const float pos = NATIVE_TO_LOGICAL(ui.processing_manual_move ? destination[axis] : current_position[axis]
       #if IS_KINEMATIC
         + manual_move_offset
       #endif
     , axis);
-    lcd_implementation_drawedit(name, move_menu_scale >= 0.1f ? ftostr41sign(pos) : ftostr43sign(pos));
+    draw_edit_screen(name, move_menu_scale >= 0.1f ? ftostr41sign(pos) : ftostr43sign(pos));
   }
 }
 void lcd_move_x() { _lcd_move_xyz(PSTR(MSG_MOVE_X), X_AXIS); }
@@ -163,11 +157,11 @@ static void _lcd_move_e(
     const int8_t eindex=-1
   #endif
 ) {
-  if (use_click()) { return lcd_goto_previous_menu_no_defer(); }
-  ENCODER_DIRECTION_NORMAL();
-  if (encoderPosition) {
-    if (!processing_manual_move) {
-      const float diff = float((int32_t)encoderPosition) * move_menu_scale;
+  if (ui.use_click()) return ui.goto_previous_screen_no_defer();
+  ui.encoder_direction_normal();
+  if (ui.encoderPosition) {
+    if (!ui.processing_manual_move) {
+      const float diff = float((int32_t)ui.encoderPosition) * move_menu_scale;
       #if IS_KINEMATIC
         manual_move_offset += diff;
       #else
@@ -178,11 +172,11 @@ static void _lcd_move_e(
           , eindex
         #endif
       );
-      lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+      ui.refresh(LCDVIEW_REDRAW_NOW);
     }
-    encoderPosition = 0;
+    ui.encoderPosition = 0;
   }
-  if (lcdDrawUpdate) {
+  if (ui.should_draw()) {
     PGM_P pos_label;
     #if E_MANUAL == 1
       pos_label = PSTR(MSG_MOVE_E);
@@ -205,7 +199,7 @@ static void _lcd_move_e(
       }
     #endif // E_MANUAL > 1
 
-    lcd_implementation_drawedit(pos_label, ftostr41sign(current_position[E_AXIS]
+    draw_edit_screen(pos_label, ftostr41sign(current_position[E_AXIS]
       #if IS_KINEMATIC
         + manual_move_offset
       #endif
@@ -241,9 +235,9 @@ inline void lcd_move_e() { _lcd_move_e(); }
 screenFunc_t _manual_move_func_ptr;
 
 void _goto_manual_move(const float scale) {
-  set_defer_return_to_status(true);
+  ui.defer_status_screen(true);
   move_menu_scale = scale;
-  lcd_goto_screen(_manual_move_func_ptr);
+  ui.goto_screen(_manual_move_func_ptr);
 }
 void menu_move_10mm() { _goto_manual_move(10); }
 void menu_move_1mm()  { _goto_manual_move( 1); }
@@ -305,7 +299,7 @@ void lcd_move_get_e_amount() { _menu_move_distance(E_AXIS, lcd_move_e, -1); }
 #if ENABLED(DELTA)
   void lcd_lower_z_to_clip_height() {
     line_to_z(delta_clip_start_height);
-    lcd_synchronize();
+    ui.synchronize();
   }
 #endif
 
diff --git a/Marlin/src/lcd/menu/menu_sdcard.cpp b/Marlin/src/lcd/menu/menu_sdcard.cpp
index 7677025207..85caa17a23 100644
--- a/Marlin/src/lcd/menu/menu_sdcard.cpp
+++ b/Marlin/src/lcd/menu/menu_sdcard.cpp
@@ -39,35 +39,36 @@
 #endif
 
 void lcd_sd_updir() {
-  encoderPosition = card.updir() ? ENCODER_STEPS_PER_MENU_ITEM : 0;
+  ui.encoderPosition = card.updir() ? ENCODER_STEPS_PER_MENU_ITEM : 0;
   encoderTopLine = 0;
   screen_changed = true;
-  lcd_refresh();
+  ui.refresh();
 }
 
 #if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
+
   uint32_t last_sdfile_encoderPosition = 0xFFFF;
 
-  void lcd_reselect_last_file() {
+  void MarlinUI::reselect_last_file() {
     if (last_sdfile_encoderPosition == 0xFFFF) return;
-    #if HAS_GRAPHICAL_LCD
-      // Some of this is a hack to force the screen update to work.
-      // TODO: Fix the real issue that causes this!
-      lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
-      lcd_synchronize();
-      safe_delay(50);
-      lcd_synchronize();
-      lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
-      drawing_screen = screen_changed = true;
-    #endif
-
-    lcd_goto_screen(menu_sdcard, last_sdfile_encoderPosition);
-    set_defer_return_to_status(true);
+    //#if HAS_GRAPHICAL_LCD
+    //  // This is a hack to force a screen update.
+    //  ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
+    //  ui.synchronize();
+    //  safe_delay(50);
+    //  ui.synchronize();
+    //  ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
+    //  ui.drawing_screen = screen_changed = true;
+    //#endif
+
+    goto_screen(menu_sdcard, last_sdfile_encoderPosition);
     last_sdfile_encoderPosition = 0xFFFF;
 
-    #if HAS_GRAPHICAL_LCD
-      lcd_update();
-    #endif
+    defer_status_screen(true);
+
+    //#if HAS_GRAPHICAL_LCD
+    //  update();
+    //#endif
   }
 #endif
 
@@ -75,30 +76,30 @@ 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
+        last_sdfile_encoderPosition = ui.encoderPosition;  // Save which file was selected for later use
       #endif
       card.openAndPrintFile(theCard.filename);
-      lcd_return_to_status();
-      lcd_reset_status();
+      ui.return_to_status();
+      ui.reset_status();
     }
 };
 
-class menu_item_sddirectory {
+class menu_item_sdfolder {
   public:
     static void action(CardReader &theCard) {
       card.chdir(theCard.filename);
       encoderTopLine = 0;
-      encoderPosition = 2 * ENCODER_STEPS_PER_MENU_ITEM;
+      ui.encoderPosition = 2 * ENCODER_STEPS_PER_MENU_ITEM;
       screen_changed = true;
       #if HAS_GRAPHICAL_LCD
-        drawing_screen = false;
+        ui.drawing_screen = false;
       #endif
-      lcd_refresh();
+      ui.refresh();
     }
 };
 
 void menu_sdcard() {
-  ENCODER_DIRECTION_MENUS();
+  ui.encoder_direction_menus();
 
   const uint16_t fileCnt = card.get_num_Files();
 
@@ -125,7 +126,7 @@ void menu_sdcard() {
       card.getfilename_sorted(nr);
 
       if (card.filenameIsDir)
-        MENU_ITEM(sddirectory, MSG_CARD_MENU, card);
+        MENU_ITEM(sdfolder, MSG_CARD_MENU, card);
       else
         MENU_ITEM(sdfile, MSG_CARD_MENU, card);
     }
diff --git a/Marlin/src/lcd/menu/menu_temperature.cpp b/Marlin/src/lcd/menu/menu_temperature.cpp
index 2f0434dc69..2883fe752e 100644
--- a/Marlin/src/lcd/menu/menu_temperature.cpp
+++ b/Marlin/src/lcd/menu/menu_temperature.cpp
@@ -36,8 +36,8 @@
 #endif
 
 // Initialized by settings.load()
-int16_t lcd_preheat_hotend_temp[2], lcd_preheat_bed_temp[2];
-uint8_t lcd_preheat_fan_speed[2];
+int16_t MarlinUI::preheat_hotend_temp[2], MarlinUI::preheat_bed_temp[2];
+uint8_t MarlinUI::preheat_fan_speed[2];
 
 //
 // "Temperature" submenu items
@@ -59,44 +59,44 @@ void _lcd_preheat(const int16_t endnum, const int16_t temph, const int16_t tempb
   #else
     UNUSED(fan);
   #endif
-  lcd_return_to_status();
+  ui.return_to_status();
 }
 
 #if HOTENDS > 1
 
-  void lcd_preheat_m1_e1_only() { _lcd_preheat(1, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); }
-  void lcd_preheat_m2_e1_only() { _lcd_preheat(1, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); }
+  void lcd_preheat_m1_e1_only() { _lcd_preheat(1, ui.preheat_hotend_temp[0], -1, ui.preheat_fan_speed[0]); }
+  void lcd_preheat_m2_e1_only() { _lcd_preheat(1, ui.preheat_hotend_temp[1], -1, ui.preheat_fan_speed[1]); }
   #if HAS_HEATED_BED
-    void lcd_preheat_m1_e1() { _lcd_preheat(1, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
-    void lcd_preheat_m2_e1() { _lcd_preheat(1, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+    void lcd_preheat_m1_e1() { _lcd_preheat(1, ui.preheat_hotend_temp[0], ui.preheat_bed_temp[0], ui.preheat_fan_speed[0]); }
+    void lcd_preheat_m2_e1() { _lcd_preheat(1, ui.preheat_hotend_temp[1], ui.preheat_bed_temp[1], ui.preheat_fan_speed[1]); }
   #endif
   #if HOTENDS > 2
-    void lcd_preheat_m1_e2_only() { _lcd_preheat(2, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); }
-    void lcd_preheat_m2_e2_only() { _lcd_preheat(2, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); }
+    void lcd_preheat_m1_e2_only() { _lcd_preheat(2, ui.preheat_hotend_temp[0], -1, ui.preheat_fan_speed[0]); }
+    void lcd_preheat_m2_e2_only() { _lcd_preheat(2, ui.preheat_hotend_temp[1], -1, ui.preheat_fan_speed[1]); }
     #if HAS_HEATED_BED
-      void lcd_preheat_m1_e2() { _lcd_preheat(2, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
-      void lcd_preheat_m2_e2() { _lcd_preheat(2, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+      void lcd_preheat_m1_e2() { _lcd_preheat(2, ui.preheat_hotend_temp[0], ui.preheat_bed_temp[0], ui.preheat_fan_speed[0]); }
+      void lcd_preheat_m2_e2() { _lcd_preheat(2, ui.preheat_hotend_temp[1], ui.preheat_bed_temp[1], ui.preheat_fan_speed[1]); }
     #endif
     #if HOTENDS > 3
-      void lcd_preheat_m1_e3_only() { _lcd_preheat(3, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); }
-      void lcd_preheat_m2_e3_only() { _lcd_preheat(3, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); }
+      void lcd_preheat_m1_e3_only() { _lcd_preheat(3, ui.preheat_hotend_temp[0], -1, ui.preheat_fan_speed[0]); }
+      void lcd_preheat_m2_e3_only() { _lcd_preheat(3, ui.preheat_hotend_temp[1], -1, ui.preheat_fan_speed[1]); }
       #if HAS_HEATED_BED
-        void lcd_preheat_m1_e3() { _lcd_preheat(3, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
-        void lcd_preheat_m2_e3() { _lcd_preheat(3, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+        void lcd_preheat_m1_e3() { _lcd_preheat(3, ui.preheat_hotend_temp[0], ui.preheat_bed_temp[0], ui.preheat_fan_speed[0]); }
+        void lcd_preheat_m2_e3() { _lcd_preheat(3, ui.preheat_hotend_temp[1], ui.preheat_bed_temp[1], ui.preheat_fan_speed[1]); }
       #endif
       #if HOTENDS > 4
-        void lcd_preheat_m1_e4_only() { _lcd_preheat(4, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); }
-        void lcd_preheat_m2_e4_only() { _lcd_preheat(4, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); }
+        void lcd_preheat_m1_e4_only() { _lcd_preheat(4, ui.preheat_hotend_temp[0], -1, ui.preheat_fan_speed[0]); }
+        void lcd_preheat_m2_e4_only() { _lcd_preheat(4, ui.preheat_hotend_temp[1], -1, ui.preheat_fan_speed[1]); }
         #if HAS_HEATED_BED
-          void lcd_preheat_m1_e4() { _lcd_preheat(4, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
-          void lcd_preheat_m2_e4() { _lcd_preheat(4, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+          void lcd_preheat_m1_e4() { _lcd_preheat(4, ui.preheat_hotend_temp[0], ui.preheat_bed_temp[0], ui.preheat_fan_speed[0]); }
+          void lcd_preheat_m2_e4() { _lcd_preheat(4, ui.preheat_hotend_temp[1], ui.preheat_bed_temp[1], ui.preheat_fan_speed[1]); }
         #endif
         #if HOTENDS > 5
-          void lcd_preheat_m1_e5_only() { _lcd_preheat(5, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); }
-          void lcd_preheat_m2_e5_only() { _lcd_preheat(5, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); }
+          void lcd_preheat_m1_e5_only() { _lcd_preheat(5, ui.preheat_hotend_temp[0], -1, ui.preheat_fan_speed[0]); }
+          void lcd_preheat_m2_e5_only() { _lcd_preheat(5, ui.preheat_hotend_temp[1], -1, ui.preheat_fan_speed[1]); }
           #if HAS_HEATED_BED
-            void lcd_preheat_m1_e5() { _lcd_preheat(5, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
-            void lcd_preheat_m2_e5() { _lcd_preheat(5, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+            void lcd_preheat_m1_e5() { _lcd_preheat(5, ui.preheat_hotend_temp[0], ui.preheat_bed_temp[0], ui.preheat_fan_speed[0]); }
+            void lcd_preheat_m2_e5() { _lcd_preheat(5, ui.preheat_hotend_temp[1], ui.preheat_bed_temp[1], ui.preheat_fan_speed[1]); }
           #endif
         #endif // HOTENDS > 5
       #endif // HOTENDS > 4
@@ -113,15 +113,15 @@ void _lcd_preheat(const int16_t endnum, const int16_t temph, const int16_t tempb
 
   void lcd_preheat_m1_all() {
     #if HOTENDS > 1
-      thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 1);
+      thermalManager.setTargetHotend(ui.preheat_hotend_temp[0], 1);
       #if HOTENDS > 2
-        thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 2);
+        thermalManager.setTargetHotend(ui.preheat_hotend_temp[0], 2);
         #if HOTENDS > 3
-          thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 3);
+          thermalManager.setTargetHotend(ui.preheat_hotend_temp[0], 3);
           #if HOTENDS > 4
-            thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 4);
+            thermalManager.setTargetHotend(ui.preheat_hotend_temp[0], 4);
             #if HOTENDS > 5
-              thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 5);
+              thermalManager.setTargetHotend(ui.preheat_hotend_temp[0], 5);
             #endif // HOTENDS > 5
           #endif // HOTENDS > 4
         #endif // HOTENDS > 3
@@ -136,15 +136,15 @@ void _lcd_preheat(const int16_t endnum, const int16_t temph, const int16_t tempb
 
   void lcd_preheat_m2_all() {
     #if HOTENDS > 1
-      thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 1);
+      thermalManager.setTargetHotend(ui.preheat_hotend_temp[1], 1);
       #if HOTENDS > 2
-        thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 2);
+        thermalManager.setTargetHotend(ui.preheat_hotend_temp[1], 2);
         #if HOTENDS > 3
-          thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 3);
+          thermalManager.setTargetHotend(ui.preheat_hotend_temp[1], 3);
           #if HOTENDS > 4
-            thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 4);
+            thermalManager.setTargetHotend(ui.preheat_hotend_temp[1], 4);
             #if HOTENDS > 5
-              thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 5);
+              thermalManager.setTargetHotend(ui.preheat_hotend_temp[1], 5);
             #endif // HOTENDS > 5
           #endif // HOTENDS > 4
         #endif // HOTENDS > 3
@@ -161,14 +161,14 @@ void _lcd_preheat(const int16_t endnum, const int16_t temph, const int16_t tempb
 
 #if HAS_TEMP_HOTEND || HAS_HEATED_BED
 
-  void lcd_preheat_m1_e0_only() { _lcd_preheat(0, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); }
-  void lcd_preheat_m2_e0_only() { _lcd_preheat(0, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); }
+  void lcd_preheat_m1_e0_only() { _lcd_preheat(0, ui.preheat_hotend_temp[0], -1, ui.preheat_fan_speed[0]); }
+  void lcd_preheat_m2_e0_only() { _lcd_preheat(0, ui.preheat_hotend_temp[1], -1, ui.preheat_fan_speed[1]); }
 
   #if HAS_HEATED_BED
-    void lcd_preheat_m1_e0() { _lcd_preheat(0, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
-    void lcd_preheat_m2_e0() { _lcd_preheat(0, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
-    void lcd_preheat_m1_bedonly() { _lcd_preheat(0, 0, lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); }
-    void lcd_preheat_m2_bedonly() { _lcd_preheat(0, 0, lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); }
+    void lcd_preheat_m1_e0() { _lcd_preheat(0, ui.preheat_hotend_temp[0], ui.preheat_bed_temp[0], ui.preheat_fan_speed[0]); }
+    void lcd_preheat_m2_e0() { _lcd_preheat(0, ui.preheat_hotend_temp[1], ui.preheat_bed_temp[1], ui.preheat_fan_speed[1]); }
+    void lcd_preheat_m1_bedonly() { _lcd_preheat(0, 0, ui.preheat_bed_temp[0], ui.preheat_fan_speed[0]); }
+    void lcd_preheat_m2_bedonly() { _lcd_preheat(0, 0, ui.preheat_bed_temp[1], ui.preheat_fan_speed[1]); }
   #endif
 
   void menu_preheat_m1() {
@@ -294,7 +294,7 @@ void _lcd_preheat(const int16_t endnum, const int16_t temph, const int16_t tempb
   void lcd_cooldown() {
     zero_fan_speeds();
     thermalManager.disable_all_heaters();
-    lcd_return_to_status();
+    ui.return_to_status();
   }
 
 #endif // HAS_TEMP_HOTEND || HAS_HEATED_BED
diff --git a/Marlin/src/lcd/menu/menu_tune.cpp b/Marlin/src/lcd/menu/menu_tune.cpp
index b4f3476585..b3e41fa5d0 100644
--- a/Marlin/src/lcd/menu/menu_tune.cpp
+++ b/Marlin/src/lcd/menu/menu_tune.cpp
@@ -62,29 +62,29 @@ void _lcd_refresh_e_factor_0() { planner.refresh_e_factor(0); }
   long babysteps_done = 0;
 
   void _lcd_babystep(const AxisEnum axis, PGM_P msg) {
-    if (use_click()) { return lcd_goto_previous_menu_no_defer(); }
-    ENCODER_DIRECTION_NORMAL();
-    if (encoderPosition) {
-      const int16_t babystep_increment = (int32_t)encoderPosition * (BABYSTEP_MULTIPLICATOR);
-      encoderPosition = 0;
-      lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+    if (ui.use_click()) return ui.goto_previous_screen_no_defer();
+    ui.encoder_direction_normal();
+    if (ui.encoderPosition) {
+      const int16_t babystep_increment = (int32_t)ui.encoderPosition * (BABYSTEP_MULTIPLICATOR);
+      ui.encoderPosition = 0;
+      ui.refresh(LCDVIEW_REDRAW_NOW);
       thermalManager.babystep_axis(axis, babystep_increment);
       babysteps_done += babystep_increment;
     }
-    if (lcdDrawUpdate)
-      lcd_implementation_drawedit(msg, ftostr43sign(planner.steps_to_mm[axis] * babysteps_done));
+    if (ui.should_draw())
+      draw_edit_screen(msg, ftostr43sign(planner.steps_to_mm[axis] * babysteps_done));
   }
 
   #if ENABLED(BABYSTEP_XY)
     void _lcd_babystep_x() { _lcd_babystep(X_AXIS, PSTR(MSG_BABYSTEP_X)); }
     void _lcd_babystep_y() { _lcd_babystep(Y_AXIS, PSTR(MSG_BABYSTEP_Y)); }
-    void lcd_babystep_x() { lcd_goto_screen(_lcd_babystep_x); babysteps_done = 0; set_defer_return_to_status(true); }
-    void lcd_babystep_y() { lcd_goto_screen(_lcd_babystep_y); babysteps_done = 0; set_defer_return_to_status(true); }
+    void lcd_babystep_x() { ui.goto_screen(_lcd_babystep_x); babysteps_done = 0; ui.defer_status_screen(true); }
+    void lcd_babystep_y() { ui.goto_screen(_lcd_babystep_y); babysteps_done = 0; ui.defer_status_screen(true); }
   #endif
 
   #if DISABLED(BABYSTEP_ZPROBE_OFFSET)
     void _lcd_babystep_z() { _lcd_babystep(Z_AXIS, PSTR(MSG_BABYSTEP_Z)); }
-    void lcd_babystep_z() { lcd_goto_screen(_lcd_babystep_z); babysteps_done = 0; set_defer_return_to_status(true); }
+    void lcd_babystep_z() { ui.goto_screen(_lcd_babystep_z); babysteps_done = 0; ui.defer_status_screen(true); }
   #endif
 
 #endif // BABYSTEPPING
diff --git a/Marlin/src/lcd/menu/menu_ubl.cpp b/Marlin/src/lcd/menu/menu_ubl.cpp
index 2686e6b0c3..146c9268f1 100644
--- a/Marlin/src/lcd/menu/menu_ubl.cpp
+++ b/Marlin/src/lcd/menu/menu_ubl.cpp
@@ -51,22 +51,22 @@ float mesh_edit_value, mesh_edit_accumulator; // We round mesh_edit_value to 2.5
 static int16_t ubl_encoderPosition = 0;
 
 static void _lcd_mesh_fine_tune(PGM_P msg) {
-  set_defer_return_to_status(true);
+  ui.defer_status_screen(true);
   if (ubl.encoder_diff) {
     ubl_encoderPosition = (ubl.encoder_diff > 0) ? 1 : -1;
     ubl.encoder_diff = 0;
 
     mesh_edit_accumulator += float(ubl_encoderPosition) * 0.005f * 0.5f;
     mesh_edit_value = mesh_edit_accumulator;
-    encoderPosition = 0;
-    lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+    ui.encoderPosition = 0;
+    ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
 
     const int32_t rounded = (int32_t)(mesh_edit_value * 1000);
     mesh_edit_value = float(rounded - (rounded % 5L)) / 1000;
   }
 
-  if (lcdDrawUpdate) {
-    lcd_implementation_drawedit(msg, ftostr43sign(mesh_edit_value));
+  if (ui.should_draw()) {
+    draw_edit_screen(msg, ftostr43sign(mesh_edit_value));
     #if ENABLED(MESH_EDIT_GFX_OVERLAY)
       _lcd_zoffset_overlay_gfx(mesh_edit_value);
     #endif
@@ -74,19 +74,19 @@ static void _lcd_mesh_fine_tune(PGM_P msg) {
 }
 
 void _lcd_mesh_edit_NOP() {
-  set_defer_return_to_status(true);
+  ui.defer_status_screen(true);
 }
 
 float lcd_mesh_edit() {
-  lcd_goto_screen(_lcd_mesh_edit_NOP);
-  lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT;
+  ui.goto_screen(_lcd_mesh_edit_NOP);
+  ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
   _lcd_mesh_fine_tune(PSTR("Mesh Editor"));
   return mesh_edit_value;
 }
 
 void lcd_mesh_edit_setup(const float &initial) {
   mesh_edit_value = mesh_edit_accumulator = initial;
-  lcd_goto_screen(_lcd_mesh_edit_NOP);
+  ui.goto_screen(_lcd_mesh_edit_NOP);
 }
 
 void _lcd_z_offset_edit() {
@@ -94,13 +94,13 @@ void _lcd_z_offset_edit() {
 }
 
 float lcd_z_offset_edit() {
-  lcd_goto_screen(_lcd_z_offset_edit);
+  ui.goto_screen(_lcd_z_offset_edit);
   return mesh_edit_value;
 }
 
 void lcd_z_offset_edit_setup(const float &initial) {
   mesh_edit_value = mesh_edit_accumulator = initial;
-  lcd_goto_screen(_lcd_z_offset_edit);
+  ui.goto_screen(_lcd_z_offset_edit);
 }
 
 /**
@@ -160,7 +160,7 @@ void _menu_ubl_height_adjust() {
   START_MENU();
   MENU_BACK(MSG_EDIT_MESH);
   MENU_ITEM_EDIT_CALLBACK(int3, MSG_UBL_MESH_HEIGHT_AMOUNT, &ubl_height_amount, -9, 9, _lcd_ubl_adjust_height_cmd);
-  MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+  MENU_ITEM(function, MSG_WATCH, ui.return_to_status);
   END_MENU();
 }
 
@@ -179,7 +179,7 @@ void _lcd_ubl_edit_mesh() {
   MENU_ITEM(gcode, MSG_UBL_FINE_TUNE_ALL, PSTR("G29 P4 R999 T"));
   MENU_ITEM(gcode, MSG_UBL_FINE_TUNE_CLOSEST, PSTR("G29 P4 T"));
   MENU_ITEM(submenu, MSG_UBL_MESH_HEIGHT_ADJUST, _menu_ubl_height_adjust);
-  MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+  MENU_ITEM(function, MSG_WATCH, ui.return_to_status);
   END_MENU();
 }
 
@@ -220,7 +220,7 @@ void _lcd_ubl_validate_mesh() {
     MENU_ITEM(gcode, MSG_UBL_VALIDATE_MESH_M2, PSTR("G28\nG26 C B0 H" STRINGIFY(PREHEAT_2_TEMP_HOTEND) " P"));
   #endif
   MENU_ITEM(function, MSG_UBL_VALIDATE_CUSTOM_MESH, _lcd_ubl_validate_custom_mesh);
-  MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+  MENU_ITEM(function, MSG_WATCH, ui.return_to_status);
   END_MENU();
 }
 
@@ -261,7 +261,7 @@ void _lcd_ubl_mesh_leveling() {
   MENU_BACK(MSG_UBL_TOOLS);
   MENU_ITEM(gcode, MSG_UBL_3POINT_MESH_LEVELING, PSTR("G29 J0"));
   MENU_ITEM(submenu, MSG_UBL_GRID_MESH_LEVELING, _lcd_ubl_grid_level);
-  MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+  MENU_ITEM(function, MSG_WATCH, ui.return_to_status);
   END_MENU();
 }
 
@@ -290,7 +290,7 @@ void _menu_ubl_fillin() {
   MENU_ITEM_EDIT_CALLBACK(int3, MSG_UBL_FILLIN_AMOUNT, &ubl_fillin_amount, 0, 9, _lcd_ubl_fillin_amount_cmd);
   MENU_ITEM(gcode, MSG_UBL_SMART_FILLIN, PSTR("G29 P3 T0"));
   MENU_ITEM(gcode, MSG_UBL_MANUAL_FILLIN, PSTR("G29 P2 B T0"));
-  MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+  MENU_ITEM(function, MSG_WATCH, ui.return_to_status);
   END_MENU();
 }
 
@@ -353,7 +353,7 @@ void _lcd_ubl_build_mesh() {
   MENU_ITEM(gcode, MSG_UBL_CONTINUE_MESH, PSTR("G29 P1 C"));
   MENU_ITEM(function, MSG_UBL_INVALIDATE_ALL, _lcd_ubl_invalidate);
   MENU_ITEM(gcode, MSG_UBL_INVALIDATE_CLOSEST, PSTR("G29 I"));
-  MENU_ITEM(function, MSG_WATCH, lcd_return_to_status);
+  MENU_ITEM(function, MSG_WATCH, ui.return_to_status);
   END_MENU();
 }
 
@@ -408,11 +408,11 @@ void _lcd_ubl_storage_mesh() {
 void _lcd_ubl_output_map_lcd();
 
 void _lcd_ubl_map_homing() {
-  set_defer_return_to_status(true);
+  ui.defer_status_screen(true);
   _lcd_draw_homing();
   if (all_axes_homed()) {
     ubl.lcd_map_control = true; // Return to the map screen
-    lcd_goto_screen(_lcd_ubl_output_map_lcd);
+    ui.goto_screen(_lcd_ubl_output_map_lcd);
   }
 }
 
@@ -444,10 +444,10 @@ void sync_plan_position();
 
 void _lcd_do_nothing() {}
 void _lcd_hard_stop() {
-  const screenFunc_t old_screen = currentScreen;
-  currentScreen = _lcd_do_nothing;
+  const screenFunc_t old_screen = ui.currentScreen;
+  ui.currentScreen = _lcd_do_nothing;
   planner.quick_stop();
-  currentScreen = old_screen;
+  ui.currentScreen = old_screen;
   set_current_from_steppers_for_axis(ALL_AXES);
   sync_plan_position();
 }
@@ -455,15 +455,15 @@ void _lcd_hard_stop() {
 void _lcd_ubl_output_map_lcd() {
   static int16_t step_scaler = 0;
 
-  if (use_click()) return _lcd_ubl_map_lcd_edit_cmd();
-  ENCODER_DIRECTION_NORMAL();
+  if (ui.use_click()) return _lcd_ubl_map_lcd_edit_cmd();
+  ui.encoder_direction_normal();
 
-  if (encoderPosition) {
-    step_scaler += (int32_t)encoderPosition;
+  if (ui.encoderPosition) {
+    step_scaler += (int32_t)ui.encoderPosition;
     x_plot += step_scaler / (ENCODER_STEPS_PER_MENU_ITEM);
     if (ABS(step_scaler) >= ENCODER_STEPS_PER_MENU_ITEM) step_scaler = 0;
-    encoderPosition = 0;
-    lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+    ui.encoderPosition = 0;
+    ui.refresh(LCDVIEW_REDRAW_NOW);
   }
 
   // Encoder to the right (++)
@@ -487,8 +487,8 @@ void _lcd_ubl_output_map_lcd() {
     n_edit_pts = yc ? (xc ? 9 : 6) : (xc ? 6 : 4); // Corners
   #endif
 
-  if (lcdDrawUpdate) {
-    lcd_implementation_ubl_plot(x_plot, y_plot);
+  if (ui.should_draw()) {
+    ui.ubl_plot(x_plot, y_plot);
 
     if (planner.movesplanned()) // If the nozzle is already moving, cancel the move.
       _lcd_hard_stop();
@@ -505,7 +505,7 @@ void _lcd_ubl_output_map_lcd_cmd() {
     set_all_unhomed();
     enqueue_and_echo_commands_P(PSTR("G28"));
   }
-  lcd_goto_screen(_lcd_ubl_map_homing);
+  ui.goto_screen(_lcd_ubl_map_homing);
 }
 
 /**
diff --git a/Marlin/src/lcd/ultralcd.cpp b/Marlin/src/lcd/ultralcd.cpp
index 14b8e0d1a4..5b639e18e9 100644
--- a/Marlin/src/lcd/ultralcd.cpp
+++ b/Marlin/src/lcd/ultralcd.cpp
@@ -22,11 +22,14 @@
 
 #include "../inc/MarlinConfigPre.h"
 
-#if ENABLED(ULTRA_LCD)
+#if HAS_SPI_LCD
 
 #include <stdarg.h>
 
 #include "ultralcd.h"
+
+MarlinUI ui;
+
 #include "lcdprint.h"
 
 #include "../sd/cardreader.h"
@@ -49,19 +52,12 @@
 
 #if ENABLED(POWER_LOSS_RECOVERY)
   #include "../feature/power_loss_recovery.h"
-  #if HAS_LCD_MENU
-    void menu_job_recovery();
-  #endif
 #endif
 
 #if ENABLED(PRINTCOUNTER) && ENABLED(LCD_INFO_MENU)
   #include "../libs/duration_t.h"
 #endif
 
-#if ENABLED(FILAMENT_LCD_DISPLAY)
-  #include "../feature/filwidth.h"
-#endif
-
 #if ENABLED(BLTOUCH)
   #include "../module/endstops.h"
 #endif
@@ -74,15 +70,19 @@
   #include "../libs/buzzer.h"
 #endif
 
-// Buttons
-volatile uint8_t buttons;
+#if HAS_ENCODER_ACTION
+  volatile uint8_t MarlinUI::buttons;
+  #if ENABLED(LCD_HAS_SLOW_BUTTONS)
+    volatile uint8_t MarlinUI::slow_buttons;
+  #endif
+#endif
 
 #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
   uint8_t lcd_sd_status;
 #endif
 
 #if ENABLED(STATUS_MESSAGE_SCROLLING)
-  uint8_t status_scroll_offset = 0;
+  uint8_t MarlinUI::status_scroll_offset; // = 0
   #if LONG_FILENAME_LENGTH > CHARSIZE * 2 * (LCD_WIDTH)
     #define MAX_MESSAGE_LENGTH LONG_FILENAME_LENGTH
   #else
@@ -92,69 +92,84 @@ volatile uint8_t buttons;
   #define MAX_MESSAGE_LENGTH CHARSIZE * (LCD_WIDTH)
 #endif
 
-char lcd_status_message[MAX_MESSAGE_LENGTH + 1];
-uint8_t lcd_status_update_delay = 1, // First update one loop delayed
-        lcd_status_message_level;    // Higher level blocks lower level
-
-#if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
-  millis_t previous_lcd_status_ms = 0;
+#if HAS_LCD_MENU && LCD_TIMEOUT_TO_STATUS
+  bool MarlinUI::defer_return_to_status;
 #endif
 
-#if HAS_LCD_MENU && ENABLED(SDSUPPORT) && ENABLED(SCROLL_LONG_FILENAMES)
-  uint8_t filename_scroll_pos, filename_scroll_max;
+char MarlinUI::status_message[MAX_MESSAGE_LENGTH + 1];
+uint8_t MarlinUI::lcd_status_update_delay = 1; // First update one loop delayed
+uint8_t MarlinUI::status_message_level; // = 0
+
+#if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
+  millis_t MarlinUI::next_filament_display; // = 0
 #endif
 
 #if ENABLED(LCD_SET_PROGRESS_MANUALLY)
-  uint8_t progress_bar_percent;
+  uint8_t MarlinUI::progress_bar_percent; // = 0
 #endif
 
 millis_t next_button_update_ms;
 
 #if HAS_GRAPHICAL_LCD
-  bool drawing_screen, first_page; // = false
+  bool MarlinUI::drawing_screen, MarlinUI::first_page; // = false
 #endif
 
 // Encoder Handling
 #if HAS_ENCODER_ACTION
-  uint32_t encoderPosition;
-  volatile int8_t encoderDiff; // Updated in lcd_buttons_update, added to encoderPosition every LCD update
-  #if ENABLED(ENCODER_RATE_MULTIPLIER)
-    bool encoderRateMultiplierEnabled;
-  #endif
-  #if ENABLED(REVERSE_MENU_DIRECTION)
-    int8_t encoderDirection = 1;
-  #endif
+  uint32_t MarlinUI::encoderPosition;
+  volatile int8_t encoderDiff; // Updated in update_buttons, added to encoderPosition every LCD update
 #endif
 
 #if HAS_LCD_MENU
   #include "menu/menu.h"
 
-  screenFunc_t currentScreen = lcd_status_screen;
+  #if ENABLED(SDSUPPORT) && ENABLED(SCROLL_LONG_FILENAMES)
+    uint8_t MarlinUI::filename_scroll_pos, MarlinUI::filename_scroll_max;
+  #endif
+
+  screenFunc_t MarlinUI::currentScreen; // Initialized in CTOR
 
   #if ENABLED(ENCODER_RATE_MULTIPLIER)
-    millis_t lastEncoderMovementMillis = 0;
+    bool MarlinUI::encoderRateMultiplierEnabled;
+    millis_t MarlinUI::lastEncoderMovementMillis = 0;
+    void MarlinUI::enable_encoder_multiplier(const bool onoff) {
+      encoderRateMultiplierEnabled = onoff;
+      lastEncoderMovementMillis = 0;
+    }
   #endif
 
-  bool lcd_clicked, wait_for_unclick;
+  #if ENABLED(REVERSE_MENU_DIRECTION)
+    int8_t MarlinUI::encoderDirection = 1;
+  #endif
+
+  bool MarlinUI::lcd_clicked;
   float move_menu_scale;
 
-  bool use_click() {
+  bool MarlinUI::use_click() {
     const bool click = lcd_clicked;
     lcd_clicked = false;
     return click;
   }
 
-#else
+  #if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION)
 
-  constexpr bool lcd_clicked = false;
+    bool MarlinUI::external_control; // = false
+
+    void MarlinUI::wait_for_release() {
+      while (button_pressed()) safe_delay(50);
+      safe_delay(50);
+    }
+
+  #endif
 
 #endif
 
-void lcd_init() {
+void MarlinUI::init() {
+
+  init_lcd();
 
-  lcd_implementation_init();
+  #if HAS_DIGITAL_ENCODER
 
-  #if ENABLED(NEWPANEL)
     #if BUTTON_EXISTS(EN1)
       SET_INPUT_PULLUP(BTN_EN1);
     #endif
@@ -184,7 +199,7 @@ void lcd_init() {
       SET_INPUT(BTN_RT);
     #endif
 
-  #else // !NEWPANEL
+  #else // !HAS_DIGITAL_ENCODER
 
     #if ENABLED(SR_LCD_2W_NL) // Non latching 2 wire shift register
       SET_OUTPUT(SR_DATA_PIN);
@@ -196,25 +211,25 @@ void lcd_init() {
       SET_INPUT_PULLUP(SHIFT_OUT);
     #endif // SR_LCD_2W_NL
 
-  #endif // !NEWPANEL
+  #endif // !HAS_DIGITAL_ENCODER
 
   #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
     SET_INPUT_PULLUP(SD_DETECT_PIN);
     lcd_sd_status = 2; // UNKNOWN
   #endif
 
-  #if ENABLED(LCD_HAS_SLOW_BUTTONS)
+  #if HAS_ENCODER_ACTION && ENABLED(LCD_HAS_SLOW_BUTTONS)
     slow_buttons = 0;
   #endif
 
-  lcd_buttons_update();
+  update_buttons();
 
   #if HAS_ENCODER_ACTION
     encoderDiff = 0;
   #endif
 }
 
-bool lcd_blink() {
+bool MarlinUI::get_blink() {
   static uint8_t blink = 0;
   static millis_t next_blink_ms = 0;
   millis_t ms = millis();
@@ -239,13 +254,13 @@ bool lcd_blink() {
     #define ADC_MIN_KEY_DELAY 100
     if (buttons_reprapworld_keypad) {
       #if HAS_ENCODER_ACTION
-        lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+        ui.refresh(LCDVIEW_REDRAW_NOW);
         if (encoderDirection == -1) { // side effect which signals we are inside a menu
           #if HAS_LCD_MENU
             if      (RRK(EN_REPRAPWORLD_KEYPAD_DOWN))   encoderPosition -= ENCODER_STEPS_PER_MENU_ITEM;
             else if (RRK(EN_REPRAPWORLD_KEYPAD_UP))     encoderPosition += ENCODER_STEPS_PER_MENU_ITEM;
-            else if (RRK(EN_REPRAPWORLD_KEYPAD_LEFT))   { menu_item_back::action(); lcd_quick_feedback(); }
-            else if (RRK(EN_REPRAPWORLD_KEYPAD_RIGHT))  { lcd_return_to_status(); lcd_quick_feedback(); }
+            else if (RRK(EN_REPRAPWORLD_KEYPAD_LEFT))   { menu_item_back::action(); ui.quick_feedback(); }
+            else if (RRK(EN_REPRAPWORLD_KEYPAD_RIGHT))  { ui.return_to_status(); ui.quick_feedback(); }
           #endif
         }
         else if (RRK(EN_REPRAPWORLD_KEYPAD_DOWN))     encoderPosition += ENCODER_PULSES_PER_STEP;
@@ -298,7 +313,7 @@ bool lcd_blink() {
 
       #if HAS_LCD_MENU
 
-        if (RRK(EN_REPRAPWORLD_KEYPAD_MIDDLE))  lcd_goto_screen(menu_move);
+        if (RRK(EN_REPRAPWORLD_KEYPAD_MIDDLE))  ui.goto_screen(menu_move);
 
         #if DISABLED(DELTA) && Z_HOME_DIR == -1
           if (RRK(EN_REPRAPWORLD_KEYPAD_F2))    _reprapworld_keypad_move(Z_AXIS,  1);
@@ -330,32 +345,33 @@ bool lcd_blink() {
  */
 
 #if ENABLED(LCD_PROGRESS_BAR)
-  millis_t progress_bar_ms = 0;     // Start millis of the current progress bar cycle
+  millis_t MarlinUI::progress_bar_ms; // = 0
   #if PROGRESS_MSG_EXPIRE > 0
-    static millis_t expire_status_ms = 0;
-    void dontExpireStatus() { expire_status_ms = 0; }
+    millis_t MarlinUI::expire_status_ms; // = 0
   #endif
 #endif
 
-#if LCD_INFO_SCREEN_STYLE == 0
-  void lcd_impl_status_screen_0();
-#elif LCD_INFO_SCREEN_STYLE == 1
-  void lcd_impl_status_screen_1();
+#if HAS_PRINT_PROGRESS
+  uint8_t MarlinUI::get_progress() {
+    #if ENABLED(LCD_SET_PROGRESS_MANUALLY)
+      uint8_t &progress = progress_bar_percent;
+    #else
+      uint8_t progress = 0;
+    #endif
+    #if ENABLED(SDSUPPORT)
+      if (IS_SD_PRINTING()) progress = card.percentDone();
+    #endif
+    return progress;
+  }
 #endif
 
-void lcd_status_screen() {
+void MarlinUI::status_screen() {
 
   #if HAS_LCD_MENU
-    ENCODER_DIRECTION_NORMAL();
+    encoder_direction_normal();
     ENCODER_RATE_MULTIPLY(false);
   #endif
 
-  #if ENABLED(LCD_SET_PROGRESS_MANUALLY) && ENABLED(SDSUPPORT) && (ENABLED(LCD_PROGRESS_BAR) || HAS_GRAPHICAL_LCD)
-    // Progress bar % comes from SD when actively printing
-    if (IS_SD_PRINTING())
-      progress_bar_percent = card.percentDone();
-  #endif
-
   #if ENABLED(LCD_PROGRESS_BAR)
 
     //
@@ -377,14 +393,10 @@ void lcd_status_screen() {
       // Handle message expire
       if (expire_status_ms > 0) {
 
-        #if DISABLED(LCD_SET_PROGRESS_MANUALLY)
-          const uint8_t progress_bar_percent = card.percentDone();
-        #endif
-
         // Expire the message if a job is active and the bar has ticks
-        if (progress_bar_percent > 2 && !print_job_timer.isPaused()) {
+        if (get_progress() > 2 && !print_job_timer.isPaused()) {
           if (ELAPSED(ms, expire_status_ms)) {
-            lcd_status_message[0] = '\0';
+            status_message[0] = '\0';
             expire_status_ms = 0;
           }
         }
@@ -403,10 +415,10 @@ void lcd_status_screen() {
 
     if (use_click()) {
       #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
-        previous_lcd_status_ms = millis();  // get status message to show up for a while
+        next_filament_display = millis() + 5000UL;  // Show status message for 5s
       #endif
-      lcd_goto_screen(menu_main);
-      lcd_implementation_init(); // May revive the LCD if static electricity killed it
+      goto_screen(menu_main);
+      init_lcd(); // May revive the LCD if static electricity killed it
       return;
     }
 
@@ -439,17 +451,13 @@ void lcd_status_screen() {
 
   #endif // ULTIPANEL_FEEDMULTIPLY
 
-  #if LCD_INFO_SCREEN_STYLE == 0
-    lcd_impl_status_screen_0();
-  #elif LCD_INFO_SCREEN_STYLE == 1
-    lcd_impl_status_screen_1();
-  #endif
+  draw_status_screen();
 }
 
 /**
  * Reset the status message
  */
-void lcd_reset_status() {
+void MarlinUI::reset_status() {
   static const char paused[] PROGMEM = MSG_PRINT_PAUSED;
   static const char printing[] PROGMEM = MSG_PRINTING;
   static const char welcome[] PROGMEM = WELCOME_MSG;
@@ -458,36 +466,29 @@ void lcd_reset_status() {
     msg = paused;
   #if ENABLED(SDSUPPORT)
     else if (IS_SD_PRINTING())
-      return lcd_setstatus(card.longest_filename(), true);
+      return setstatus(card.longest_filename(), true);
   #endif
   else if (print_job_timer.isRunning())
     msg = printing;
   else
     msg = welcome;
 
-  lcd_setstatusPGM(msg, -1);
+  setstatusPGM(msg, -1);
 }
 
-void kill_screen(PGM_P lcd_msg) {
-  lcd_init();
-  lcd_setalertstatusPGM(lcd_msg);
-  lcd_kill_screen();
+void MarlinUI::kill_screen(PGM_P lcd_msg) {
+  init();
+  setalertstatusPGM(lcd_msg);
+  draw_kill_screen();
 }
 
-#if HAS_BUZZER
-  void lcd_buzz(const long duration, const uint16_t freq) {
-    #if ENABLED(LCD_USE_I2C_BUZZER)
-      lcd.buzz(duration, freq);
-    #elif PIN_EXISTS(BEEPER)
-      buzzer.tone(duration, freq);
-    #endif
-  }
-#endif
-
-void lcd_quick_feedback(const bool clear_buttons/*=true*/) {
+void MarlinUI::quick_feedback(const bool clear_buttons/*=true*/) {
 
   #if HAS_LCD_MENU
-    lcd_refresh();
+    refresh();
+  #endif
+
+  #if HAS_ENCODER_ACTION
     if (clear_buttons) buttons = 0;
     next_button_update_ms = millis() + 500;
   #else
@@ -495,7 +496,7 @@ void lcd_quick_feedback(const bool clear_buttons/*=true*/) {
   #endif
 
   // Buzz and wait. The delay is needed for buttons to settle!
-  lcd_buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ);
+  buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ);
 
   #if HAS_LCD_MENU
     #if ENABLED(LCD_USE_I2C_BUZZER)
@@ -514,21 +515,19 @@ void lcd_quick_feedback(const bool clear_buttons/*=true*/) {
   millis_t manual_move_start_time = 0;
 
   #if IS_KINEMATIC
-    bool processing_manual_move = false;
+    bool MarlinUI::processing_manual_move = false;
     float manual_move_offset = 0;
   #endif
 
-  #if !IS_KINEMATIC || (IS_KINEMATIC && EXTRUDERS > 1)
-    int8_t manual_move_e_index = 0;
-  #else
-    constexpr int8_t manual_move_e_index = 0;
+  #if E_MANUAL > 1
+    int8_t MarlinUI::manual_move_e_index = 0;
   #endif
 
   /**
    * If the most recent manual move hasn't been fed to the planner yet,
    * and the planner can accept one, send a move immediately.
    */
-  void manage_manual_move() {
+  void MarlinUI::manage_manual_move() {
 
     if (processing_manual_move) return;
 
@@ -583,7 +582,7 @@ void lcd_quick_feedback(const bool clear_buttons/*=true*/) {
  *   - Act on RepRap World keypad input
  *   - Update the encoder position
  *   - Apply acceleration to the encoder position
- *   - Set lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NOW on controller events
+ *   - Do refresh(LCDVIEW_CALL_REDRAW_NOW) on controller events
  *   - Reset the Info Screen timeout if there's any input
  *   - Update status indicators, if any
  *
@@ -593,7 +592,7 @@ void lcd_quick_feedback(const bool clear_buttons/*=true*/) {
  *   - Call the menu handler. Menu handlers should do the following:
  *     - If a value changes, set lcdDrawUpdate to LCDVIEW_REDRAW_NOW and draw the value
  *       (Encoder events automatically set lcdDrawUpdate for you.)
- *     - if (lcdDrawUpdate) { redraw }
+ *     - if (should_draw()) { redraw }
  *     - Before exiting the handler set lcdDrawUpdate to:
  *       - LCDVIEW_CLEAR_CALL_REDRAW to clear screen and set LCDVIEW_CALL_REDRAW_NEXT.
  *       - LCDVIEW_REDRAW_NOW to draw now (including remaining stripes).
@@ -609,17 +608,9 @@ void lcd_quick_feedback(const bool clear_buttons/*=true*/) {
  * This function is only called from the main thread.
  */
 
-LCDViewAction lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW;
+LCDViewAction MarlinUI::lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW;
 
-#if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION)
-  bool lcd_external_control; // = false
-#endif
-
-#if ENABLED(LCD_HAS_SLOW_BUTTONS)
-  volatile uint8_t slow_buttons;
-#endif
-
-bool lcd_detected() {
+bool MarlinUI::detected() {
   return
     #if (ENABLED(LCD_I2C_TYPE_MCP23017) || ENABLED(LCD_I2C_TYPE_MCP23008)) && defined(DETECT_DEVICE)
       lcd.LcdDetected() == 1
@@ -629,7 +620,7 @@ bool lcd_detected() {
   ;
 }
 
-void lcd_update() {
+void MarlinUI::update() {
 
   static uint16_t max_display_update_time = 0;
   static millis_t next_lcd_update_ms;
@@ -643,33 +634,26 @@ void lcd_update() {
     // Handle any queued Move Axis motion
     manage_manual_move();
 
-    // Update button states for LCD_CLICKED(), etc.
-    // After state changes the next button update
-    // may be delayed 300-500ms.
-    lcd_buttons_update();
-
-    #if ENABLED(AUTO_BED_LEVELING_UBL)
-      // Don't run the debouncer if UBL owns the display
-      #define UBL_CONDITION !lcd_external_control
-    #else
-      #define UBL_CONDITION true
-    #endif
+    // Update button states for button_pressed(), etc.
+    // If the state changes the next update may be delayed 300-500ms.
+    update_buttons();
 
     // If the action button is pressed...
-    if (UBL_CONDITION && LCD_CLICKED()) {
-      if (!wait_for_unclick) {           // If not waiting for a debounce release:
-        wait_for_unclick = true;         //  Set debounce flag to ignore continous clicks
-        lcd_clicked = !wait_for_user && !no_reentry; //  Keep the click if not waiting for a user-click
-        wait_for_user = false;           //  Any click clears wait for user
-        lcd_quick_feedback();        //  Always make a click sound
+    static bool wait_for_unclick; // = 0
+    if (!external_control && button_pressed()) {
+      if (!wait_for_unclick) {                        // If not waiting for a debounce release:
+        wait_for_unclick = true;                      //  - Set debounce flag to ignore continous clicks
+        lcd_clicked = !wait_for_user && !no_reentry;  //  - Keep the click if not waiting for a user-click
+        wait_for_user = false;                        //  - Any click clears wait for user
+        quick_feedback();                             //  - Always make a click sound
       }
     }
     else wait_for_unclick = false;
 
     #if BUTTON_EXISTS(BACK)
-      if (LCD_BACK_CLICKED) {
-        lcd_quick_feedback();
-        lcd_goto_previous_menu();
+      if (LCD_BACK_CLICKED()) {
+        quick_feedback();
+        goto_previous_screen();
       }
     #endif
 
@@ -678,7 +662,7 @@ void lcd_update() {
   #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
 
     const uint8_t sd_status = (uint8_t)IS_SD_INSERTED();
-    if (sd_status != lcd_sd_status && lcd_detected()) {
+    if (sd_status != lcd_sd_status && detected()) {
 
       uint8_t old_sd_status = lcd_sd_status; // prevent re-entry to this block!
       lcd_sd_status = sd_status;
@@ -689,22 +673,22 @@ void lcd_update() {
         if (old_sd_status == 2)
           card.beginautostart();  // Initial boot
         else
-          LCD_MESSAGEPGM(MSG_SD_INSERTED);
+          setstatusPGM(PSTR(MSG_SD_INSERTED));
       }
       else {
         card.release();
-        if (old_sd_status != 2) LCD_MESSAGEPGM(MSG_SD_REMOVED);
+        if (old_sd_status != 2) setstatusPGM(PSTR(MSG_SD_REMOVED));
       }
 
-      lcd_refresh();
-      lcd_implementation_init(); // May revive the LCD if static electricity killed it
+      refresh();
+      init_lcd(); // May revive the LCD if static electricity killed it
     }
 
   #endif // SDSUPPORT && SD_DETECT_PIN
 
   #if ENABLED(POWER_LOSS_RECOVERY)
     if (job_recovery_commands_count && job_recovery_phase == JOB_RECOVERY_IDLE) {
-      lcd_goto_screen(menu_job_recovery);
+      goto_screen(menu_job_recovery);
       job_recovery_phase = JOB_RECOVERY_MAYBE; // Waiting for a response
     }
   #endif
@@ -719,19 +703,15 @@ void lcd_update() {
     next_lcd_update_ms = ms + LCD_UPDATE_INTERVAL;
 
     #if ENABLED(LCD_HAS_STATUS_INDICATORS)
-      lcd_implementation_update_indicators();
+      update_indicators();
     #endif
 
-    #if HAS_LCD_MENU
+    #if HAS_ENCODER_ACTION
 
       #if ENABLED(LCD_HAS_SLOW_BUTTONS)
-        slow_buttons = lcd_implementation_read_slow_buttons(); // buttons which take too long to read in interrupt context
+        slow_buttons = read_slow_buttons(); // Buttons that take too long to read in interrupt context
       #endif
 
-    #endif // HAS_LCD_MENU
-
-    #if HAS_ENCODER_ACTION
-
       #if ENABLED(ADC_KEYPAD)
 
         if (handle_adc_keypad()) {
@@ -746,7 +726,8 @@ void lcd_update() {
 
       #endif
 
-      const bool encoderPastThreshold = (ABS(encoderDiff) >= ENCODER_PULSES_PER_STEP);
+      const float abs_diff = ABS(encoderDiff);
+      const bool encoderPastThreshold = (abs_diff >= (ENCODER_PULSES_PER_STEP));
       if (encoderPastThreshold || lcd_clicked) {
         if (encoderPastThreshold) {
 
@@ -755,12 +736,12 @@ void lcd_update() {
             int32_t encoderMultiplier = 1;
 
             if (encoderRateMultiplierEnabled) {
-              int32_t encoderMovementSteps = ABS(encoderDiff) / ENCODER_PULSES_PER_STEP;
+              const float encoderMovementSteps = abs_diff / (ENCODER_PULSES_PER_STEP);
 
               if (lastEncoderMovementMillis) {
                 // Note that the rate is always calculated between two passes through the
                 // loop and that the abs of the encoderDiff value is tracked.
-                float encoderStepRate = float(encoderMovementSteps) / float(ms - lastEncoderMovementMillis) * 1000;
+                const float encoderStepRate = encoderMovementSteps / float(ms - lastEncoderMovementMillis) * 1000;
 
                 if (encoderStepRate >= ENCODER_100X_STEPS_PER_SEC)     encoderMultiplier = 100;
                 else if (encoderStepRate >= ENCODER_10X_STEPS_PER_SEC) encoderMultiplier = 10;
@@ -784,32 +765,27 @@ void lcd_update() {
 
           #endif // ENCODER_RATE_MULTIPLIER
 
-          encoderPosition += (encoderDiff * encoderMultiplier) / ENCODER_PULSES_PER_STEP;
+          encoderPosition += (encoderDiff * encoderMultiplier) / (ENCODER_PULSES_PER_STEP);
           encoderDiff = 0;
         }
         #if HAS_LCD_MENU && LCD_TIMEOUT_TO_STATUS
           return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS;
         #endif
-        lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+        refresh(LCDVIEW_REDRAW_NOW);
       }
 
     #endif
 
     // This runs every ~100ms when idling often enough.
     // Instead of tracking changes just redraw the Status Screen once per second.
-    if (
-      #if HAS_LCD_MENU
-        currentScreen == lcd_status_screen &&
-      #endif
-      !lcd_status_update_delay--
-    ) {
+    if (on_status_screen() && !lcd_status_update_delay--) {
       lcd_status_update_delay = 9
         #if HAS_GRAPHICAL_LCD
           + 3
         #endif
       ;
       max_display_update_time--;
-      lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+      refresh(LCDVIEW_REDRAW_NOW);
     }
 
     #if HAS_LCD_MENU && ENABLED(SCROLL_LONG_FILENAMES)
@@ -817,7 +793,7 @@ void lcd_update() {
       // cause a refresh to occur until all the text has scrolled into view.
       if (currentScreen == menu_sdcard && filename_scroll_pos < filename_scroll_max && !lcd_status_update_delay--) {
         lcd_status_update_delay = 6;
-        lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+        refresh(LCDVIEW_REDRAW_NOW);
         filename_scroll_pos++;
         #if LCD_TIMEOUT_TO_STATUS
           return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS;
@@ -828,22 +804,16 @@ void lcd_update() {
     // then we want to use 1/2 of the time only.
     uint16_t bbr2 = planner.block_buffer_runtime() >> 1;
 
-    #if HAS_GRAPHICAL_LCD
-      const bool &is_drawing = drawing_screen;
-    #else
-      constexpr bool is_drawing = false;
-    #endif
-
-    if ((lcdDrawUpdate || is_drawing) && (!bbr2 || bbr2 > max_display_update_time)) {
+    if ((should_draw() || drawing_screen) && (!bbr2 || bbr2 > max_display_update_time)) {
 
       // Change state of drawing flag between screen updates
-      if (!is_drawing) switch (lcdDrawUpdate) {
+      if (!drawing_screen) switch (lcdDrawUpdate) {
         case LCDVIEW_CALL_NO_REDRAW:
-          lcdDrawUpdate = LCDVIEW_NONE;
+          refresh(LCDVIEW_NONE);
           break;
         case LCDVIEW_CLEAR_CALL_REDRAW:
         case LCDVIEW_CALL_REDRAW_NEXT:
-          lcdDrawUpdate = LCDVIEW_REDRAW_NOW;
+          refresh(LCDVIEW_REDRAW_NOW);
         case LCDVIEW_REDRAW_NOW:        // set above, or by a handler through LCDVIEW_CALL_REDRAW_NEXT
         case LCDVIEW_NONE:
           break;
@@ -853,33 +823,25 @@ void lcd_update() {
         buttons_reprapworld_keypad = 0;
       #endif
 
-      #if HAS_LCD_MENU
-        #define CURRENTSCREEN() (*currentScreen)()
-      #else
-        #define CURRENTSCREEN() lcd_status_screen()
-      #endif
-
       #if HAS_GRAPHICAL_LCD
+
         #if ENABLED(LIGHTWEIGHT_UI)
-          #if HAS_LCD_MENU
-            const bool in_status = currentScreen == lcd_status_screen;
-          #else
-            constexpr bool in_status = true;
-          #endif
-          const bool do_u8g_loop = !in_status;
+          const bool in_status = on_status_screen(),
+                     do_u8g_loop = !in_status;
           lcd_in_status(in_status);
-          if (in_status) lcd_status_screen();
+          if (in_status) status_screen();
         #else
           constexpr bool do_u8g_loop = true;
         #endif
+
         if (do_u8g_loop) {
           if (!drawing_screen) {                        // If not already drawing pages
             u8g.firstPage();                            // Start the first page
             drawing_screen = first_page = true;         // Flag as drawing pages
           }
-          lcd_setFont(FONT_MENU);                       // Setup font for every page draw
+          set_font(FONT_MENU);                       // Setup font for every page draw
           u8g.setColorIndex(1);                         // And reset the color
-          CURRENTSCREEN();                              // Draw and process the current screen
+          run_current_screen();                         // Draw and process the current screen
           first_page = false;
 
           // The screen handler can clear drawing_screen for an action that changes the screen.
@@ -890,8 +852,11 @@ void lcd_update() {
             return;
           }
         }
+
       #else
-        CURRENTSCREEN();
+
+        run_current_screen();
+
       #endif
 
       #if HAS_LCD_MENU
@@ -905,18 +870,18 @@ void lcd_update() {
 
     #if HAS_LCD_MENU && LCD_TIMEOUT_TO_STATUS
       // Return to Status Screen after a timeout
-      if (currentScreen == lcd_status_screen || defer_return_to_status)
+      if (on_status_screen() || defer_return_to_status)
         return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS;
       else if (ELAPSED(ms, return_to_status_ms))
-        lcd_return_to_status();
+        return_to_status();
     #endif
 
     // Change state of drawing flag between screen updates
-    if (!is_drawing) switch (lcdDrawUpdate) {
+    if (!drawing_screen) switch (lcdDrawUpdate) {
       case LCDVIEW_CLEAR_CALL_REDRAW:
-        lcd_implementation_clear(); break;
+        clear_lcd(); break;
       case LCDVIEW_REDRAW_NOW:
-        lcdDrawUpdate = LCDVIEW_NONE;
+        refresh(LCDVIEW_NONE);
       case LCDVIEW_NONE:
       case LCDVIEW_CALL_REDRAW_NEXT:
       case LCDVIEW_CALL_NO_REDRAW:
@@ -926,7 +891,7 @@ void lcd_update() {
   } // ELAPSED(ms, next_lcd_update_ms)
 }
 
-void lcd_finishstatus(const bool persist=false) {
+void MarlinUI::finishstatus(const bool persist) {
 
   #if !(ENABLED(LCD_PROGRESS_BAR) && (PROGRESS_MSG_EXPIRE > 0))
     UNUSED(persist);
@@ -939,21 +904,21 @@ void lcd_finishstatus(const bool persist=false) {
     #endif
   #endif
 
-  lcd_refresh();
-
   #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
-    previous_lcd_status_ms = millis();  //get status message to show up for a while
+    next_filament_display = millis() + 5000UL; // Show status message for 5s
   #endif
 
   #if ENABLED(STATUS_MESSAGE_SCROLLING)
     status_scroll_offset = 0;
   #endif
+
+  refresh();
 }
 
-bool lcd_hasstatus() { return (lcd_status_message[0] != '\0'); }
+bool MarlinUI::hasstatus() { return (status_message[0] != '\0'); }
 
-void lcd_setstatus(const char * const message, const bool persist) {
-  if (lcd_status_message_level > 0) return;
+void MarlinUI::setstatus(const char * const message, const bool persist) {
+  if (status_message_level > 0) return;
 
   // Here we have a problem. The message is encoded in UTF8, so
   // arbitrarily cutting it will be a problem. We MUST be sure
@@ -971,16 +936,16 @@ void lcd_setstatus(const char * const message, const bool persist) {
 
   // At this point, we have the proper cut point. Use it
   uint8_t maxLen = pend - message;
-  strncpy(lcd_status_message, message, maxLen);
-  lcd_status_message[maxLen] = '\0';
+  strncpy(status_message, message, maxLen);
+  status_message[maxLen] = '\0';
 
-  lcd_finishstatus(persist);
+  finishstatus(persist);
 }
 
-void lcd_setstatusPGM(PGM_P const message, int8_t level) {
-  if (level < 0) level = lcd_status_message_level = 0;
-  if (level < lcd_status_message_level) return;
-  lcd_status_message_level = level;
+void MarlinUI::setstatusPGM(PGM_P const message, int8_t level) {
+  if (level < 0) level = status_message_level = 0;
+  if (level < status_message_level) return;
+  status_message_level = level;
 
   // Here we have a problem. The message is encoded in UTF8, so
   // arbitrarily cutting it will be a problem. We MUST be sure
@@ -998,31 +963,29 @@ void lcd_setstatusPGM(PGM_P const message, int8_t level) {
 
   // At this point, we have the proper cut point. Use it
   uint8_t maxLen = pend - message;
-  strncpy_P(lcd_status_message, message, maxLen);
-  lcd_status_message[maxLen] = '\0';
+  strncpy_P(status_message, message, maxLen);
+  status_message[maxLen] = '\0';
 
-  lcd_finishstatus(level > 0);
+  finishstatus(level > 0);
 }
 
-void lcd_status_printf_P(const uint8_t level, PGM_P const fmt, ...) {
-  if (level < lcd_status_message_level) return;
-  lcd_status_message_level = level;
+void MarlinUI::status_printf_P(const uint8_t level, PGM_P const fmt, ...) {
+  if (level < status_message_level) return;
+  status_message_level = level;
   va_list args;
   va_start(args, fmt);
-  vsnprintf_P(lcd_status_message, MAX_MESSAGE_LENGTH, fmt, args);
+  vsnprintf_P(status_message, MAX_MESSAGE_LENGTH, fmt, args);
   va_end(args);
-  lcd_finishstatus(level > 0);
+  finishstatus(level > 0);
 }
 
-void lcd_setalertstatusPGM(PGM_P const message) {
-  lcd_setstatusPGM(message, 1);
+void MarlinUI::setalertstatusPGM(PGM_P const message) {
+  setstatusPGM(message, 1);
   #if HAS_LCD_MENU
-    lcd_return_to_status();
+    return_to_status();
   #endif
 }
 
-void lcd_reset_alert_level() { lcd_status_message_level = 0; }
-
 #if ENABLED(ADC_KEYPAD)
 
   typedef struct {
@@ -1058,13 +1021,29 @@ void lcd_reset_alert_level() { lcd_status_message_level = 0; }
   }
 #endif
 
-#if HAS_LCD_MENU
+#if HAS_ENCODER_ACTION
+
+  #if DISABLED(ADC_KEYPAD) && (ENABLED(REPRAPWORLD_KEYPAD) || !HAS_DIGITAL_ENCODER)
+
+    /**
+     * Setup Rotary Encoder Bit Values (for two pin encoders to indicate movement)
+     * These values are independent of which pins are used for EN_A and EN_B indications
+     * The rotary encoder part is also independent to the chipset used for the LCD
+     */
+    #define GET_SHIFT_BUTTON_STATES(DST) \
+      uint8_t new_##DST = 0; \
+      WRITE(SHIFT_LD, LOW); \
+      WRITE(SHIFT_LD, HIGH); \
+      for (int8_t i = 0; i < 8; i++) { \
+        new_##DST >>= 1; \
+        if (READ(SHIFT_OUT)) SBI(new_##DST, 7); \
+        WRITE(SHIFT_CLK, HIGH); \
+        WRITE(SHIFT_CLK, LOW); \
+      } \
+      DST = ~new_##DST; //invert it, because a pressed switch produces a logical 0
+
+  #endif
 
-  /**
-   * Setup Rotary Encoder Bit Values (for two pin encoders to indicate movement)
-   * These values are independent of which pins are used for EN_A and EN_B indications
-   * The rotary encoder part is also independent to the chipset used for the LCD
-   */
   #if defined(EN_A) && defined(EN_B)
     #define encrot0 0
     #define encrot1 2
@@ -1072,42 +1051,16 @@ void lcd_reset_alert_level() { lcd_status_message_level = 0; }
     #define encrot3 1
   #endif
 
-  #define GET_SHIFT_BUTTON_STATES(DST) \
-    uint8_t new_##DST = 0; \
-    WRITE(SHIFT_LD, LOW); \
-    WRITE(SHIFT_LD, HIGH); \
-    for (int8_t i = 0; i < 8; i++) { \
-      new_##DST >>= 1; \
-      if (READ(SHIFT_OUT)) SBI(new_##DST, 7); \
-      WRITE(SHIFT_CLK, HIGH); \
-      WRITE(SHIFT_CLK, LOW); \
-    } \
-    DST = ~new_##DST; //invert it, because a pressed switch produces a logical 0
-
-  #if ENABLED(G26_MESH_VALIDATION)
-    void lcd_chirp() {
-      lcd_buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ);
-    }
-  #endif
-
-  #if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION)
-    bool is_lcd_clicked() { return LCD_CLICKED(); }
-    void wait_for_release() {
-      while (is_lcd_clicked()) safe_delay(50);
-      safe_delay(50);
-    }
-  #endif
-
   /**
    * Read encoder buttons from the hardware registers
    * Warning: This function is called from interrupt context!
    */
-  void lcd_buttons_update() {
+  void MarlinUI::update_buttons() {
     static uint8_t lastEncoderBits;
     const millis_t now = millis();
     if (ELAPSED(now, next_button_update_ms)) {
 
-      #if ENABLED(NEWPANEL)
+      #if HAS_DIGITAL_ENCODER
         uint8_t newbutton = 0;
 
         #if BUTTON_EXISTS(EN1)
@@ -1128,53 +1081,43 @@ void lcd_reset_alert_level() { lcd_status_message_level = 0; }
         //
         #if LCD_HAS_DIRECTIONAL_BUTTONS
 
-          #if ENABLED(REVERSE_MENU_DIRECTION)
-            #define _ENCODER_UD_STEPS (ENCODER_STEPS_PER_MENU_ITEM * encoderDirection)
-          #else
-            #define _ENCODER_UD_STEPS ENCODER_STEPS_PER_MENU_ITEM
-          #endif
-          #if ENABLED(REVERSE_ENCODER_DIRECTION)
-            #define ENCODER_UD_STEPS _ENCODER_UD_STEPS
-            #define ENCODER_LR_PULSES ENCODER_PULSES_PER_STEP
-          #else
-            #define ENCODER_UD_STEPS -(_ENCODER_UD_STEPS)
-            #define ENCODER_LR_PULSES -(ENCODER_PULSES_PER_STEP)
-          #endif
+          const int8_t pulses = (ENCODER_PULSES_PER_STEP) * encoderDirection;
 
           if (false) {
             // for the else-ifs below
           }
           #if BUTTON_EXISTS(UP)
             else if (BUTTON_PRESSED(UP)) {
-              encoderDiff = -(ENCODER_UD_STEPS);
+              encoderDiff = (ENCODER_STEPS_PER_MENU_ITEM) * pulses;
               next_button_update_ms = now + 300;
             }
           #endif
           #if BUTTON_EXISTS(DWN)
             else if (BUTTON_PRESSED(DWN)) {
-              encoderDiff = ENCODER_UD_STEPS;
+              encoderDiff = -(ENCODER_STEPS_PER_MENU_ITEM) * pulses;
               next_button_update_ms = now + 300;
             }
           #endif
           #if BUTTON_EXISTS(LFT)
             else if (BUTTON_PRESSED(LFT)) {
-              encoderDiff = -(ENCODER_LR_PULSES);
+              encoderDiff = -pulses;
               next_button_update_ms = now + 300;
             }
           #endif
           #if BUTTON_EXISTS(RT)
             else if (BUTTON_PRESSED(RT)) {
-              encoderDiff = ENCODER_LR_PULSES;
+              encoderDiff = pulses;
               next_button_update_ms = now + 300;
             }
           #endif
 
         #endif // LCD_HAS_DIRECTIONAL_BUTTONS
 
-        buttons = newbutton;
-        #if ENABLED(LCD_HAS_SLOW_BUTTONS)
-          buttons |= slow_buttons;
-        #endif
+        buttons = newbutton
+          #if ENABLED(LCD_HAS_SLOW_BUTTONS)
+            | slow_buttons
+          #endif
+        ;
 
         #if ENABLED(ADC_KEYPAD)
 
@@ -1192,7 +1135,7 @@ void lcd_reset_alert_level() { lcd_status_message_level = 0; }
 
         #endif
 
-      #else // !NEWPANEL
+      #else // !HAS_DIGITAL_ENCODER
 
         GET_SHIFT_BUTTON_STATES(buttons);
 
@@ -1201,20 +1144,7 @@ void lcd_reset_alert_level() { lcd_status_message_level = 0; }
     } // next_button_update_ms
 
     // Manage encoder rotation
-    #if ENABLED(REVERSE_MENU_DIRECTION) && ENABLED(REVERSE_ENCODER_DIRECTION)
-      #define ENCODER_DIFF_CW  (encoderDiff -= encoderDirection)
-      #define ENCODER_DIFF_CCW (encoderDiff += encoderDirection)
-    #elif ENABLED(REVERSE_MENU_DIRECTION)
-      #define ENCODER_DIFF_CW  (encoderDiff += encoderDirection)
-      #define ENCODER_DIFF_CCW (encoderDiff -= encoderDirection)
-    #elif ENABLED(REVERSE_ENCODER_DIRECTION)
-      #define ENCODER_DIFF_CW  (encoderDiff--)
-      #define ENCODER_DIFF_CCW (encoderDiff++)
-    #else
-      #define ENCODER_DIFF_CW  (encoderDiff++)
-      #define ENCODER_DIFF_CCW (encoderDiff--)
-    #endif
-    #define ENCODER_SPIN(_E1, _E2) switch (lastEncoderBits) { case _E1: ENCODER_DIFF_CW; break; case _E2: ENCODER_DIFF_CCW; }
+    #define ENCODER_SPIN(_E1, _E2) switch (lastEncoderBits) { case _E1: encoderDiff += encoderDirection; break; case _E2: encoderDiff -= encoderDirection; }
 
     uint8_t enc = 0;
     if (buttons & EN_A) enc |= B01;
@@ -1226,16 +1156,33 @@ void lcd_reset_alert_level() { lcd_status_message_level = 0; }
         case encrot2: ENCODER_SPIN(encrot1, encrot3); break;
         case encrot3: ENCODER_SPIN(encrot2, encrot0); break;
       }
-      #if ENABLED(AUTO_BED_LEVELING_UBL)
-        if (lcd_external_control) {
+      if (external_control) {
+        #if ENABLED(AUTO_BED_LEVELING_UBL)
           ubl.encoder_diff = encoderDiff;   // Make encoder rotation available to UBL G29 mesh editing.
-          encoderDiff = 0;                  // Hide the encoder event from the current screen handler.
-        }
-      #endif
+        #endif
+        encoderDiff = 0;                    // Hide the encoder event from the current screen handler.
+      }
       lastEncoderBits = enc;
     }
   }
 
-#endif // HAS_LCD_MENU
+  #if ENABLED(LCD_HAS_SLOW_BUTTONS)
+
+    uint8_t MarlinUI::read_slow_buttons() {
+      #if ENABLED(LCD_I2C_TYPE_MCP23017)
+        // Reading these buttons this is likely to be too slow to call inside interrupt context
+        // so they are called during normal lcd_update
+        uint8_t slow_bits = lcd.readButtons() << B_I2C_BTN_OFFSET;
+        #if ENABLED(LCD_I2C_VIKI)
+          if ((slow_bits & (B_MI | B_RI)) && PENDING(millis(), next_button_update_ms)) // LCD clicked
+            slow_bits &= ~(B_MI | B_RI); // Disable LCD clicked buttons if screen is updated
+        #endif // LCD_I2C_VIKI
+        return slow_bits;
+      #endif // LCD_I2C_TYPE_MCP23017
+    }
+
+  #endif // LCD_HAS_SLOW_BUTTONS
+
+#endif // HAS_ENCODER_ACTION
 
-#endif // ULTRA_LCD
+#endif // HAS_SPI_LCD
diff --git a/Marlin/src/lcd/ultralcd.h b/Marlin/src/lcd/ultralcd.h
index 42f8e82bf7..b3a7038c95 100644
--- a/Marlin/src/lcd/ultralcd.h
+++ b/Marlin/src/lcd/ultralcd.h
@@ -23,6 +23,21 @@
 
 #include "../inc/MarlinConfig.h"
 
+#if HAS_SPI_LCD
+
+  #include "../Marlin.h"
+
+  #if ENABLED(ADVANCED_PAUSE_FEATURE)
+    #include "../feature/pause.h"
+    #include "../module/motion.h" // for active_extruder
+  #endif
+
+#endif
+
+#if HAS_BUZZER
+  #include "../libs/buzzer.h"
+#endif
+
 #if HAS_GRAPHICAL_LCD
 
   #ifndef LCD_PIXEL_WIDTH
@@ -171,13 +186,11 @@
   #define INFO_FONT_DESCENT 2
   #define INFO_FONT_HEIGHT (INFO_FONT_ASCENT + INFO_FONT_DESCENT)
 
-  // Font IDs
   enum MarlinFont : uint8_t {
     FONT_STATUSMENU = 1,
     FONT_EDIT,
     FONT_MENU
   };
-  void lcd_setFont(const MarlinFont font_nr);
 
   #if ENABLED(LIGHTWEIGHT_UI)
     void lcd_in_status(const bool inStatus);
@@ -185,46 +198,10 @@
 
 #endif // HAS_GRAPHICAL_LCD
 
-#if HAS_SPI_LCD || ENABLED(MALYAN_LCD) || ENABLED(EXTENSIBLE_UI)
-  void lcd_init();
-  bool lcd_detected();
-  void lcd_update();
-  void lcd_setalertstatusPGM(PGM_P message);
-  void kill_screen(PGM_P lcd_msg);
-#else
-  inline void lcd_init() {}
-  inline bool lcd_detected() { return true; }
-  inline void lcd_update() {}
-  inline void lcd_setalertstatusPGM(PGM_P message) { UNUSED(message); }
-#endif
-
 #define HAS_ENCODER_ACTION (HAS_LCD_MENU || ENABLED(ULTIPANEL_FEEDMULTIPLY))
 
-#if HAS_ENCODER_ACTION
-  extern uint32_t encoderPosition;
-#endif
-
 #if HAS_SPI_LCD
 
-  #include "../Marlin.h"
-
-  #if ENABLED(ADVANCED_PAUSE_FEATURE)
-    #include "../feature/pause.h"
-    #include "../module/motion.h" // for active_extruder
-  #endif
-
-  void lcd_status_screen();
-  void lcd_return_to_status();
-  bool lcd_hasstatus();
-  void lcd_setstatus(const char* message, const bool persist=false);
-  void lcd_setstatusPGM(PGM_P message, const int8_t level=0);
-  void lcd_setalertstatusPGM(PGM_P message);
-  void lcd_reset_alert_level();
-  void lcd_reset_status();
-  void lcd_status_printf_P(const uint8_t level, PGM_P const fmt, ...);
-  void lcd_kill_screen();
-  void kill_screen(PGM_P lcd_msg);
-
   enum LCDViewAction : uint8_t {
     LCDVIEW_NONE,
     LCDVIEW_REDRAW_NOW,
@@ -233,37 +210,10 @@
     LCDVIEW_CALL_NO_REDRAW
   };
 
-  extern LCDViewAction lcdDrawUpdate;
-  inline void lcd_refresh() { lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; }
-
-  #if HAS_BUZZER
-    void lcd_buzz(const long duration, const uint16_t freq);
-  #else
-    inline void lcd_buzz(const long duration, const uint16_t freq) { UNUSED(duration); UNUSED(freq); }
-  #endif
-
-  void lcd_quick_feedback(const bool clear_buttons=true); // Audible feedback for a button click - could also be visual
-
-  #if ENABLED(LCD_PROGRESS_BAR)
-    extern millis_t progress_bar_ms;  // Start time for the current progress bar cycle
-    #if PROGRESS_MSG_EXPIRE > 0
-      void dontExpireStatus();
-    #endif
-  #endif
-
-  #if ENABLED(LCD_SET_PROGRESS_MANUALLY)
-    extern uint8_t progress_bar_percent;
-  #endif
-
   #if ENABLED(ADC_KEYPAD)
     uint8_t get_ADC_keyValue();
   #endif
 
-  #if HAS_LCD_CONTRAST
-    extern int16_t lcd_contrast;
-    void set_lcd_contrast(const int16_t value);
-  #endif
-
   #if HAS_GRAPHICAL_LCD
     #define SETCURSOR(col, row) lcd_moveto(col * (MENU_FONT_WIDTH), (row + 1) * (MENU_FONT_HEIGHT))
     #define SETCURSOR_RJ(len, row) lcd_moveto(LCD_PIXEL_WIDTH - len * (MENU_FONT_WIDTH), (row + 1) * (MENU_FONT_HEIGHT))
@@ -272,10 +222,6 @@
     #define SETCURSOR_RJ(len, row) lcd_moveto(LCD_WIDTH - len, row)
   #endif
 
-  #if ENABLED(SHOW_BOOTSCREEN)
-    void lcd_bootscreen();
-  #endif
-
   #define LCD_UPDATE_INTERVAL 100
   #define BUTTON_EXISTS(BN) (defined(BTN_## BN) && BTN_## BN >= 0)
   #define BUTTON_PRESSED(BN) !READ(BTN_## BN)
@@ -284,41 +230,10 @@
 
     typedef void (*screenFunc_t)();
     typedef void (*menuAction_t)();
-    extern screenFunc_t currentScreen;
-    void lcd_goto_screen(const screenFunc_t screen, const uint32_t encoder=0);
-
-    extern bool lcd_clicked;
-    #if LCD_TIMEOUT_TO_STATUS
-      extern bool defer_return_to_status;
-      inline void set_defer_return_to_status(const bool defer) { defer_return_to_status = defer; }
-    #else
-      constexpr bool defer_return_to_status = false;
-      #define set_defer_return_to_status(D) NOOP
-    #endif
-
-    extern int16_t lcd_preheat_hotend_temp[2], lcd_preheat_bed_temp[2];
-    extern uint8_t lcd_preheat_fan_speed[2];
-
-    #if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION)
-      extern bool lcd_external_control;
-    #else
-      constexpr bool lcd_external_control = false;
-    #endif
-
-    #if ENABLED(LCD_BED_LEVELING)
-      extern bool lcd_wait_for_move;
-    #else
-      constexpr bool lcd_wait_for_move = false;
-    #endif
 
     // Manual Movement
     constexpr float manual_feedrate_mm_m[XYZE] = MANUAL_FEEDRATE;
     extern float move_menu_scale;
-    #if IS_KINEMATIC
-      extern bool processing_manual_move;
-    #else
-      constexpr bool processing_manual_move = false;
-    #endif
 
     #if ENABLED(ADVANCED_PAUSE_FEATURE)
       void lcd_advanced_pause_show_message(const AdvancedPauseMessage message,
@@ -326,65 +241,15 @@
                                            const uint8_t extruder=active_extruder);
     #endif
 
-    #if ENABLED(G26_MESH_VALIDATION)
-      void lcd_chirp();
-    #endif
-
     #if ENABLED(AUTO_BED_LEVELING_UBL)
       void lcd_mesh_edit_setup(const float &initial);
       float lcd_mesh_edit();
     #endif
 
-    #if ENABLED(SCROLL_LONG_FILENAMES)
-      extern uint8_t filename_scroll_pos, filename_scroll_max;
-    #endif
-
   #endif // HAS_LCD_MENU
 
-  #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
-    extern millis_t previous_lcd_status_ms;
-  #endif
-
-  #if ENABLED(STATUS_MESSAGE_SCROLLING)
-    extern uint8_t status_scroll_offset;
-  #endif
-
-  bool lcd_blink();
-
-  bool use_click();
-
-  #if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION)
-    bool is_lcd_clicked();
-    void wait_for_release();
-  #endif
-
-#elif ENABLED(EXTENSIBLE_UI)
-
-  // These functions are defined elsewhere
-  void lcd_setstatus(const char* const message, const bool persist=false);
-  void lcd_setstatusPGM(const char* const message, const int8_t level=0);
-  void lcd_status_printf_P(const uint8_t level, const char * const fmt, ...);
-  void lcd_reset_status();
-  void lcd_refresh();
-  void lcd_reset_alert_level();
-  bool lcd_hasstatus();
-
-#else // MALYAN_LCD or no LCD
-
-  constexpr bool lcd_wait_for_move = false;
-
-  inline void lcd_refresh() {}
-  inline bool lcd_hasstatus() { return false; }
-  inline void lcd_setstatus(const char* const message, const bool persist=false) { UNUSED(message); UNUSED(persist); }
-  inline void lcd_setstatusPGM(PGM_P const message, const int8_t level=0) { UNUSED(message); UNUSED(level); }
-  inline void lcd_status_printf_P(const uint8_t level, PGM_P const fmt, ...) { UNUSED(level); UNUSED(fmt); }
-  inline void lcd_reset_alert_level() {}
-  inline void lcd_reset_status() {}
-
 #endif
 
-#define HAS_DIGITAL_ENCODER (HAS_SPI_LCD && ENABLED(NEWPANEL))
-
 #if HAS_DIGITAL_ENCODER
 
   // Wheel spin pins where BA is 00, 10, 11, 01 (1 bit always changes)
@@ -402,103 +267,400 @@
   #if BUTTON_EXISTS(BACK)
     #define BLEN_D 3
     #define EN_D _BV(BLEN_D)
-    #define LCD_BACK_CLICKED (buttons & EN_D)
+    #define LCD_BACK_CLICKED() (buttons & EN_D)
   #endif
 
-#endif // HAS_DIGITAL_ENCODER
+  #if ENABLED(REPRAPWORLD_KEYPAD)
+    #define REPRAPWORLD_BTN_OFFSET          0 // Bit offset into buttons for shift register values
+
+    #define BLEN_REPRAPWORLD_KEYPAD_F3      0
+    #define BLEN_REPRAPWORLD_KEYPAD_F2      1
+    #define BLEN_REPRAPWORLD_KEYPAD_F1      2
+    #define BLEN_REPRAPWORLD_KEYPAD_DOWN    3
+    #define BLEN_REPRAPWORLD_KEYPAD_RIGHT   4
+    #define BLEN_REPRAPWORLD_KEYPAD_MIDDLE  5
+    #define BLEN_REPRAPWORLD_KEYPAD_UP      6
+    #define BLEN_REPRAPWORLD_KEYPAD_LEFT    7
+
+    #define EN_REPRAPWORLD_KEYPAD_F1        (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_F1))
+    #define EN_REPRAPWORLD_KEYPAD_F2        (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_F2))
+    #define EN_REPRAPWORLD_KEYPAD_F3        (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_F3))
+    #define EN_REPRAPWORLD_KEYPAD_DOWN      (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_DOWN))
+    #define EN_REPRAPWORLD_KEYPAD_RIGHT     (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_RIGHT))
+    #define EN_REPRAPWORLD_KEYPAD_MIDDLE    (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_MIDDLE))
+    #define EN_REPRAPWORLD_KEYPAD_UP        (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_UP))
+    #define EN_REPRAPWORLD_KEYPAD_LEFT      (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_LEFT))
+
+    #define RRK(B) (buttons_reprapworld_keypad & (B))
+
+    #ifdef EN_C
+      #define BUTTON_CLICK() ((buttons & EN_C) || RRK(EN_REPRAPWORLD_KEYPAD_MIDDLE))
+    #else
+      #define BUTTON_CLICK() RRK(EN_REPRAPWORLD_KEYPAD_MIDDLE)
+    #endif
 
-#if HAS_LCD_MENU
+  #elif ENABLED(LCD_I2C_VIKI)
 
-  extern volatile uint8_t buttons;  // The last-checked buttons in a bit array.
-  void lcd_buttons_update();
+    #define B_I2C_BTN_OFFSET 3 // (the first three bit positions reserved for EN_A, EN_B, EN_C)
 
-#else
+    // button and encoder bit positions within 'buttons'
+    #define B_LE (BUTTON_LEFT   << B_I2C_BTN_OFFSET)    // The remaining normalized buttons are all read via I2C
+    #define B_UP (BUTTON_UP     << B_I2C_BTN_OFFSET)
+    #define B_MI (BUTTON_SELECT << B_I2C_BTN_OFFSET)
+    #define B_DW (BUTTON_DOWN   << B_I2C_BTN_OFFSET)
+    #define B_RI (BUTTON_RIGHT  << B_I2C_BTN_OFFSET)
 
-  inline void lcd_buttons_update() {}
+    #if BUTTON_EXISTS(ENC)                                // The pause/stop/restart button is connected to BTN_ENC when used
+      #define B_ST (EN_C)                                 // Map the pause/stop/resume button into its normalized functional name
+      #define BUTTON_CLICK() (buttons & (B_MI|B_RI|B_ST))  // Pause/stop also acts as click until a proper pause/stop is implemented.
+    #else
+      #define BUTTON_CLICK() (buttons & (B_MI|B_RI))
+    #endif
 
-#endif
+    // I2C buttons take too long to read inside an interrupt context and so we read them during lcd_update
+    #define LCD_HAS_SLOW_BUTTONS
 
-#if ENABLED(LCD_HAS_SLOW_BUTTONS)
-  extern volatile uint8_t slow_buttons;
-#endif
+  #elif ENABLED(LCD_I2C_PANELOLU2)
 
-#if ENABLED(REPRAPWORLD_KEYPAD)
-  #define REPRAPWORLD_BTN_OFFSET          0 // Bit offset into buttons for shift register values
-
-  #define BLEN_REPRAPWORLD_KEYPAD_F3      0
-  #define BLEN_REPRAPWORLD_KEYPAD_F2      1
-  #define BLEN_REPRAPWORLD_KEYPAD_F1      2
-  #define BLEN_REPRAPWORLD_KEYPAD_DOWN    3
-  #define BLEN_REPRAPWORLD_KEYPAD_RIGHT   4
-  #define BLEN_REPRAPWORLD_KEYPAD_MIDDLE  5
-  #define BLEN_REPRAPWORLD_KEYPAD_UP      6
-  #define BLEN_REPRAPWORLD_KEYPAD_LEFT    7
-
-  #define EN_REPRAPWORLD_KEYPAD_F1        (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_F1))
-  #define EN_REPRAPWORLD_KEYPAD_F2        (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_F2))
-  #define EN_REPRAPWORLD_KEYPAD_F3        (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_F3))
-  #define EN_REPRAPWORLD_KEYPAD_DOWN      (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_DOWN))
-  #define EN_REPRAPWORLD_KEYPAD_RIGHT     (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_RIGHT))
-  #define EN_REPRAPWORLD_KEYPAD_MIDDLE    (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_MIDDLE))
-  #define EN_REPRAPWORLD_KEYPAD_UP        (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_UP))
-  #define EN_REPRAPWORLD_KEYPAD_LEFT      (_BV(REPRAPWORLD_BTN_OFFSET + BLEN_REPRAPWORLD_KEYPAD_LEFT))
-
-  #define RRK(B) (buttons_reprapworld_keypad & (B))
+    #if !BUTTON_EXISTS(ENC) // Use I2C if not directly connected to a pin
+
+      #define B_I2C_BTN_OFFSET 3 // (the first three bit positions reserved for EN_A, EN_B, EN_C)
+
+      #define B_MI (PANELOLU2_ENCODER_C << B_I2C_BTN_OFFSET) // requires LiquidTWI2 library v1.2.3 or later
+
+      #define BUTTON_CLICK() (buttons & B_MI)
+
+      // I2C buttons take too long to read inside an interrupt context and so we read them during lcd_update
+      #define LCD_HAS_SLOW_BUTTONS
+
+    #endif
 
-  #ifdef EN_C
-    #define LCD_CLICKED() ((buttons & EN_C) || RRK(EN_REPRAPWORLD_KEYPAD_MIDDLE))
-  #else
-    #define LCD_CLICKED() RRK(EN_REPRAPWORLD_KEYPAD_MIDDLE)
   #endif
 
-#endif // REPRAPWORLD_KEYPAD
+#else
+
+  // Shift register bits correspond to buttons:
+  #define BL_LE 7   // Left
+  #define BL_UP 6   // Up
+  #define BL_MI 5   // Middle
+  #define BL_DW 4   // Down
+  #define BL_RI 3   // Right
+  #define BL_ST 2   // Red Button
+  #define B_LE (_BV(BL_LE))
+  #define B_UP (_BV(BL_UP))
+  #define B_MI (_BV(BL_MI))
+  #define B_DW (_BV(BL_DW))
+  #define B_RI (_BV(BL_RI))
+  #define B_ST (_BV(BL_ST))
+  #define BUTTON_CLICK() (buttons & (B_MI|B_ST))
+
+#endif
 
-#ifndef LCD_CLICKED
+#ifndef BUTTON_CLICK
   #ifdef EN_C
-    #define LCD_CLICKED() (buttons & EN_C)
+    #define BUTTON_CLICK() (buttons & EN_C)
   #else
-    #define LCD_CLICKED() false
+    #define BUTTON_CLICK() false
   #endif
 #endif
 
-extern uint8_t lcd_status_update_delay;
-extern char lcd_status_message[];
+#define LCD_MESSAGEPGM(x)      ui.setstatusPGM(PSTR(x))
+#define LCD_ALERTMESSAGEPGM(x) ui.setalertstatusPGM(PSTR(x))
 
-#define LCD_MESSAGEPGM(x)      lcd_setstatusPGM(PSTR(x))
-#define LCD_ALERTMESSAGEPGM(x) lcd_setalertstatusPGM(PSTR(x))
+////////////////////////////////////////////
+//////////// MarlinUI Singleton ////////////
+////////////////////////////////////////////
 
-// For i2c define BUZZ to use lcd_buzz
-#if ENABLED(LCD_USE_I2C_BUZZER)
-  #define BUZZ(d,f) lcd_buzz(d, f)
-#endif
+class MarlinUI {
+public:
 
-#if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
-  void lcd_reselect_last_file();
-#endif
+  MarlinUI() {
+    #if HAS_LCD_MENU
+      currentScreen = status_screen;
+    #endif
+  }
 
-#if HAS_GRAPHICAL_LCD
-  extern bool drawing_screen, first_page;
-#elif HAS_SPI_LCD
-  constexpr bool first_page = true;
-#endif
+  static inline void buzz(const long duration, const uint16_t freq) {
+    #if ENABLED(LCD_USE_I2C_BUZZER)
+      lcd.buzz(duration, freq);
+    #elif PIN_EXISTS(BEEPER)
+      buzzer.tone(duration, freq);
+    #else
+      UNUSED(duration); UNUSED(freq);
+    #endif
+  }
+
+  // LCD implementations
+  static void clear_lcd();
+  static void init_lcd();
+
+  #if HAS_SPI_LCD || ENABLED(MALYAN_LCD) || ENABLED(EXTENSIBLE_UI)
+    static void init();
+    static void update();
+    static bool detected();
+    static void setalertstatusPGM(PGM_P message);
+  #else // NO LCD
+    static inline void init() {}
+    static inline void update() {}
+    static constexpr bool detected() { return true; }
+    static inline void setalertstatusPGM(PGM_P message) { UNUSED(message); }
+  #endif
+
+  #if HAS_SPI_LCD || ENABLED(EXTENSIBLE_UI)
+
+    #if HAS_SPI_LCD
+
+      static LCDViewAction lcdDrawUpdate;
+      static inline bool should_draw() { return bool(lcdDrawUpdate); }
+      static inline void refresh(const LCDViewAction type) { lcdDrawUpdate = type; }
+      static inline void refresh() { refresh(LCDVIEW_CLEAR_CALL_REDRAW); }
+
+      #if ENABLED(SHOW_BOOTSCREEN)
+        static void show_bootscreen();
+      #endif
+
+      #if HAS_GRAPHICAL_LCD
+
+        static bool drawing_screen, first_page;
+
+        static void set_font(const MarlinFont font_nr);
+
+      #else
+
+        static constexpr bool drawing_screen = false, first_page = true;
+
+        enum HD44780CharSet : uint8_t { CHARSET_MENU, CHARSET_INFO, CHARSET_BOOT };
+
+        static void set_custom_characters(
+          #if ENABLED(LCD_PROGRESS_BAR) || ENABLED(SHOW_BOOTSCREEN)
+            const HD44780CharSet screen_charset=CHARSET_INFO
+          #endif
+        );
+
+        #if ENABLED(LCD_PROGRESS_BAR)
+          static millis_t progress_bar_ms;  // Start time for the current progress bar cycle
+          #if PROGRESS_MSG_EXPIRE > 0
+            static millis_t MarlinUI::expire_status_ms; // = 0
+            static inline void reset_progress_bar_timeout() { expire_status_ms = 0; }
+          #endif
+          #define LCD_SET_CHARSET(C) set_custom_characters(C)
+        #else
+          #define LCD_SET_CHARSET(C) set_custom_characters()
+        #endif
+
+      #endif
+
+      // Status message
+      static char status_message[];
+      #if ENABLED(STATUS_MESSAGE_SCROLLING)
+        static uint8_t status_scroll_offset;
+      #endif
+
+      static uint8_t lcd_status_update_delay;
+      static uint8_t status_message_level;      // Higher levels block lower levels
+      static inline void reset_alert_level() { status_message_level = 0; }
+
+      #if HAS_PRINT_PROGRESS
+        #if ENABLED(LCD_SET_PROGRESS_MANUALLY)
+          static uint8_t progress_bar_percent;
+          static void set_progress(const uint8_t progress) { progress_bar_percent = MIN(progress, 100); }
+        #endif
+        static uint8_t get_progress();
+      #else
+        static constexpr uint8_t get_progress() { return 0; }
+      #endif
+
+      #if HAS_LCD_CONTRAST
+        static int16_t contrast;
+        static void set_contrast(const int16_t value);
+        static inline void refresh_contrast() { set_contrast(contrast); }
+      #endif
+
+      #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
+        static millis_t next_filament_display;
+      #endif
+
+      static void quick_feedback(const bool clear_buttons=true);
+      static void completion_feedback(const bool good=true);
+
+      #if DISABLED(LIGHTWEIGHT_UI)
+        static void draw_status_message(const bool blink);
+      #endif
+
+      #if ENABLED(ADVANCED_PAUSE_FEATURE)
+        static void draw_hotend_status(const uint8_t row, const uint8_t extruder);
+      #endif
+
+      static void status_screen();
+
+    #else
+
+      static void refresh();
+      static void reset_alert_level();
+
+    #endif
+
+    static bool get_blink();
+    static void kill_screen(PGM_P const lcd_msg);
+    static void draw_kill_screen();
+    static bool hasstatus();
+    static void setstatus(const char* const message, const bool persist=false);
+    static void setstatusPGM(PGM_P const message, const int8_t level=0);
+    static void status_printf_P(const uint8_t level, PGM_P const fmt, ...);
+    static void reset_status();
+
+  #else // MALYAN_LCD or NO LCD
+
+    static inline void refresh() {}
+    static constexpr bool hasstatus() { return false; }
+    static inline void setstatus(const char* const message, const bool persist=false) { UNUSED(message); UNUSED(persist); }
+    static inline void setstatusPGM(PGM_P const message, const int8_t level=0) { UNUSED(message); UNUSED(level); }
+    static inline void status_printf_P(const uint8_t level, PGM_P const fmt, ...) { UNUSED(level); UNUSED(fmt); }
+    static inline void reset_status() {}
+    static inline void reset_alert_level() {}
+
+  #endif
+
+  #if HAS_LCD_MENU
+
+    #if ENABLED(ENCODER_RATE_MULTIPLIER)
+      static bool encoderRateMultiplierEnabled;
+      static millis_t lastEncoderMovementMillis;
+      static void enable_encoder_multiplier(const bool onoff);
+    #endif
+
+    #if ENABLED(SCROLL_LONG_FILENAMES)
+      static uint8_t filename_scroll_pos, filename_scroll_max;
+    #endif
+
+    #if IS_KINEMATIC
+      static bool processing_manual_move;
+    #else
+      static constexpr bool processing_manual_move = false;
+    #endif
+
+    #if E_MANUAL > 1
+      static int8_t manual_move_e_index;
+    #else
+      static constexpr int8_t manual_move_e_index = 0;
+    #endif
 
-// LCD implementations
-void lcd_implementation_clear();
-void lcd_implementation_init();
+    static int16_t preheat_hotend_temp[2], preheat_bed_temp[2];
+    static uint8_t preheat_fan_speed[2];
 
-#if HAS_CHARACTER_LCD
+    static void manage_manual_move();
 
-  enum HD44780CharSet : uint8_t { CHARSET_MENU, CHARSET_INFO, CHARSET_BOOT };
+    static bool lcd_clicked;
+    static bool use_click();
 
-  void lcd_set_custom_characters(
-    #if ENABLED(LCD_PROGRESS_BAR) || ENABLED(SHOW_BOOTSCREEN)
-      const HD44780CharSet screen_charset=CHARSET_INFO
+    static void synchronize(PGM_P const msg=NULL);
+
+    static screenFunc_t currentScreen;
+    static void goto_screen(const screenFunc_t screen, const uint32_t encoder=0);
+    static void save_previous_screen();
+    static void goto_previous_screen();
+    static void return_to_status();
+    static inline bool on_status_screen() { return currentScreen == status_screen; }
+    static inline void run_current_screen() { (*currentScreen)(); }
+
+    static inline void defer_status_screen(const bool defer) {
+      #if LCD_TIMEOUT_TO_STATUS
+        defer_return_to_status = defer;
+      #else
+        UNUSED(defer);
+      #endif
+    }
+
+    static inline void goto_previous_screen_no_defer() {
+      defer_status_screen(false);
+      goto_previous_screen();
+    }
+
+    #if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
+      static void reselect_last_file();
     #endif
-  );
-  #if ENABLED(LCD_PROGRESS_BAR)
-    #define LCD_SET_CHARSET(C) lcd_set_custom_characters(C)
+
+    #if ENABLED(G26_MESH_VALIDATION)
+      static inline void chirp() { buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ); }
+    #endif
+
+    #if ENABLED(AUTO_BED_LEVELING_UBL)
+      static void ubl_plot(const uint8_t x, const uint8_t inverted_y);
+    #endif
+
+  #elif HAS_SPI_LCD
+
+    static constexpr bool lcd_clicked = false;
+    static constexpr bool on_status_screen() { return true; }
+    static inline void run_current_screen() { status_screen(); }
+
+  #endif
+
+  #if ENABLED(LCD_BED_LEVELING) && (ENABLED(PROBE_MANUALLY) || ENABLED(MESH_BED_LEVELING))
+    static bool wait_for_bl_move;
   #else
-    #define LCD_SET_CHARSET(C) lcd_set_custom_characters()
+    static constexpr bool wait_for_bl_move = false;
   #endif
 
-#endif
+  #if HAS_LCD_MENU && (ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION))
+    static bool external_control;
+    FORCE_INLINE static void capture() { external_control = true; }
+    FORCE_INLINE static void release() { external_control = false; }
+  #else
+    static constexpr bool external_control = false;
+  #endif
+
+  #if HAS_ENCODER_ACTION
+
+    static volatile uint8_t buttons;
+    #if ENABLED(LCD_HAS_SLOW_BUTTONS)
+      static volatile uint8_t slow_buttons;
+      static uint8_t read_slow_buttons();
+    #endif
+    static void update_buttons();
+    static inline bool button_pressed() { return BUTTON_CLICK(); }
+    #if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION)
+      static void wait_for_release();
+    #endif
+
+    static uint32_t encoderPosition;
+
+    #if ENABLED(REVERSE_ENCODER_DIRECTION)
+      #define ENCODERBASE -1
+    #else
+      #define ENCODERBASE +1
+    #endif
+    #if ENABLED(REVERSE_MENU_DIRECTION)
+      static int8_t encoderDirection;
+      static inline void encoder_direction_normal() { encoderDirection = +(ENCODERBASE); }
+      static inline void encoder_direction_menus()  { encoderDirection = -(ENCODERBASE); }
+    #else
+      static constexpr int8_t encoderDirection = ENCODERBASE;
+      static inline void encoder_direction_normal() { }
+      static inline void encoder_direction_menus()  { }
+    #endif
+
+  #else
+
+    static inline void update_buttons() { }
+
+  #endif
+
+private:
+
+  static void _synchronize();
+
+  #if HAS_SPI_LCD
+    #if HAS_LCD_MENU
+      #if LCD_TIMEOUT_TO_STATUS
+        static bool defer_return_to_status;
+      #else
+        static constexpr bool defer_return_to_status = false;
+      #endif
+    #endif
+    static void draw_status_screen();
+    static void finishstatus(const bool persist);
+  #endif
+};
+
+extern MarlinUI ui;
diff --git a/Marlin/src/libs/buzzer.h b/Marlin/src/libs/buzzer.h
index 2a150e267c..c0ed8d9fa2 100644
--- a/Marlin/src/libs/buzzer.h
+++ b/Marlin/src/libs/buzzer.h
@@ -23,94 +23,95 @@
 
 #include "../inc/MarlinConfig.h"
 
-// Make a buzzer and macro
 #if ENABLED(LCD_USE_I2C_BUZZER)
-  // BUZZ() will be defined in ultralcd.h
-#elif PIN_EXISTS(BEEPER)
-
-#include "circularqueue.h"
 
-#define TONE_QUEUE_LENGTH 4
+  #define BUZZ(d,f) ui.buzz(d,f)
 
-/**
- * @brief Tone structure
- * @details Simple abstraction of a tone based on a duration and a frequency.
- */
-struct tone_t {
-  uint16_t duration;
-  uint16_t frequency;
-};
+#elif PIN_EXISTS(BEEPER)
 
-/**
- * @brief Buzzer class
- */
-class Buzzer {
-  public:
-
-    typedef struct {
-      tone_t   tone;
-      uint32_t endtime;
-    } state_t;
-
-  private:
-    static state_t state;
-
-  protected:
-    static CircularQueue<tone_t, TONE_QUEUE_LENGTH> buffer;
-
-    /**
-     * @brief Inverts the sate of a digital PIN
-     * @details This will invert the current state of an digital IO pin.
-     */
-    FORCE_INLINE static void invert() { TOGGLE(BEEPER_PIN); }
-
-    /**
-     * @brief Turn off a digital PIN
-     * @details Alias of digitalWrite(PIN, LOW) using FastIO
-     */
-    FORCE_INLINE static void off() { WRITE(BEEPER_PIN, LOW); }
-
-    /**
-     * @brief Turn on a digital PIN
-     * @details Alias of digitalWrite(PIN, HIGH) using FastIO
-     */
-    FORCE_INLINE static void on() { WRITE(BEEPER_PIN, HIGH); }
-
-    /**
-     * @brief Resets the state of the class
-     * @details Brings the class state to a known one.
-     */
-    static inline void reset() {
-      off();
-      state.endtime = 0;
-    }
-
-  public:
-    /**
-     * @brief Class constructor
-     */
-    Buzzer() {
-      SET_OUTPUT(BEEPER_PIN);
-      reset();
-    }
-
-    /**
-     * @brief Add a tone to the queue
-     * @details Adds a tone_t structure to the ring buffer, will block IO if the
-     *          queue is full waiting for one slot to get available.
-     *
-     * @param duration Duration of the tone in milliseconds
-     * @param frequency Frequency of the tone in hertz
-     */
-    static void tone(const uint16_t duration, const uint16_t frequency=0);
-
-    /**
-     * @brief Tick function
-     * @details This function should be called at loop, it will take care of
-     *          playing the tones in the queue.
-     */
-    static void tick();
-};
+  #include "circularqueue.h"
+
+  #define TONE_QUEUE_LENGTH 4
+
+  /**
+   * @brief Tone structure
+   * @details Simple abstraction of a tone based on a duration and a frequency.
+   */
+  struct tone_t {
+    uint16_t duration;
+    uint16_t frequency;
+  };
+
+  /**
+   * @brief Buzzer class
+   */
+  class Buzzer {
+    public:
+
+      typedef struct {
+        tone_t   tone;
+        uint32_t endtime;
+      } state_t;
+
+    private:
+      static state_t state;
+
+    protected:
+      static CircularQueue<tone_t, TONE_QUEUE_LENGTH> buffer;
+
+      /**
+       * @brief Inverts the sate of a digital PIN
+       * @details This will invert the current state of an digital IO pin.
+       */
+      FORCE_INLINE static void invert() { TOGGLE(BEEPER_PIN); }
+
+      /**
+       * @brief Turn off a digital PIN
+       * @details Alias of digitalWrite(PIN, LOW) using FastIO
+       */
+      FORCE_INLINE static void off() { WRITE(BEEPER_PIN, LOW); }
+
+      /**
+       * @brief Turn on a digital PIN
+       * @details Alias of digitalWrite(PIN, HIGH) using FastIO
+       */
+      FORCE_INLINE static void on() { WRITE(BEEPER_PIN, HIGH); }
+
+      /**
+       * @brief Resets the state of the class
+       * @details Brings the class state to a known one.
+       */
+      static inline void reset() {
+        off();
+        state.endtime = 0;
+      }
+
+    public:
+      /**
+       * @brief Class constructor
+       */
+      Buzzer() {
+        SET_OUTPUT(BEEPER_PIN);
+        reset();
+      }
+
+      /**
+       * @brief Add a tone to the queue
+       * @details Adds a tone_t structure to the ring buffer, will block IO if the
+       *          queue is full waiting for one slot to get available.
+       *
+       * @param duration Duration of the tone in milliseconds
+       * @param frequency Frequency of the tone in hertz
+       */
+      static void tone(const uint16_t duration, const uint16_t frequency=0);
+
+      /**
+       * @brief Tick function
+       * @details This function should be called at loop, it will take care of
+       *          playing the tones in the queue.
+       */
+      static void tick();
+  };
 
   // Provide a buzzer instance
   extern Buzzer buzzer;
diff --git a/Marlin/src/module/configuration_store.cpp b/Marlin/src/module/configuration_store.cpp
index 9e8e9e3fc5..3919f99ea7 100644
--- a/Marlin/src/module/configuration_store.cpp
+++ b/Marlin/src/module/configuration_store.cpp
@@ -201,9 +201,9 @@ typedef struct SettingsDataStruct {
   //
   // ULTIPANEL
   //
-  int16_t lcd_preheat_hotend_temp[2],                   // M145 S0 H
-          lcd_preheat_bed_temp[2];                      // M145 S0 B
-  uint8_t lcd_preheat_fan_speed[2];                     // M145 S0 F
+  int16_t ui_preheat_hotend_temp[2],                    // M145 S0 H
+          ui_preheat_bed_temp[2];                       // M145 S0 B
+  uint8_t ui_preheat_fan_speed[2];                      // M145 S0 F
 
   //
   // PIDTEMP
@@ -680,15 +680,19 @@ void MarlinSettings::postprocess() {
     {
       _FIELD_TEST(lcd_preheat_hotend_temp);
 
-      #if !HAS_LCD_MENU
-        constexpr int16_t lcd_preheat_hotend_temp[2] = { PREHEAT_1_TEMP_HOTEND, PREHEAT_2_TEMP_HOTEND },
-                          lcd_preheat_bed_temp[2] = { PREHEAT_1_TEMP_BED, PREHEAT_2_TEMP_BED };
-        constexpr uint8_t lcd_preheat_fan_speed[2] = { PREHEAT_1_FAN_SPEED, PREHEAT_2_FAN_SPEED };
+      #if HAS_LCD_MENU
+        const int16_t (&ui_preheat_hotend_temp)[2]  = ui.preheat_hotend_temp,
+                      (&ui_preheat_bed_temp)[2]     = ui.preheat_bed_temp;
+        const uint8_t (&ui_preheat_fan_speed)[2]    = ui.preheat_fan_speed;
+      #else
+        constexpr int16_t ui_preheat_hotend_temp[2] = { PREHEAT_1_TEMP_HOTEND, PREHEAT_2_TEMP_HOTEND },
+                          ui_preheat_bed_temp[2]    = { PREHEAT_1_TEMP_BED, PREHEAT_2_TEMP_BED };
+        constexpr uint8_t ui_preheat_fan_speed[2]   = { PREHEAT_1_FAN_SPEED, PREHEAT_2_FAN_SPEED };
       #endif
 
-      EEPROM_WRITE(lcd_preheat_hotend_temp);
-      EEPROM_WRITE(lcd_preheat_bed_temp);
-      EEPROM_WRITE(lcd_preheat_fan_speed);
+      EEPROM_WRITE(ui_preheat_hotend_temp);
+      EEPROM_WRITE(ui_preheat_bed_temp);
+      EEPROM_WRITE(ui_preheat_fan_speed);
     }
 
     //
@@ -717,6 +721,7 @@ void MarlinSettings::postprocess() {
     //
     {
       _FIELD_TEST(bedPID);
+
       #if DISABLED(PIDTEMPBED)
         const PID_t bed_pid = { DUMMY_PID_VALUE, DUMMY_PID_VALUE, DUMMY_PID_VALUE };
         EEPROM_WRITE(bed_pid);
@@ -731,9 +736,13 @@ void MarlinSettings::postprocess() {
     {
       _FIELD_TEST(lcd_contrast);
 
-      #if !HAS_LCD_CONTRAST
-        const int16_t lcd_contrast = 32;
-      #endif
+      const int16_t lcd_contrast =
+        #if HAS_LCD_CONTRAST
+          ui.contrast
+        #else
+          32
+        #endif
+      ;
       EEPROM_WRITE(lcd_contrast);
     }
 
@@ -1304,15 +1313,19 @@ void MarlinSettings::postprocess() {
       // LCD Preheat settings
       //
       {
-        _FIELD_TEST(lcd_preheat_hotend_temp);
+        _FIELD_TEST(ui_preheat_hotend_temp);
 
-        #if !HAS_LCD_MENU
-          int16_t lcd_preheat_hotend_temp[2], lcd_preheat_bed_temp[2];
-          uint8_t lcd_preheat_fan_speed[2];
+        #if HAS_LCD_MENU
+          int16_t (&ui_preheat_hotend_temp)[2]  = ui.preheat_hotend_temp,
+                  (&ui_preheat_bed_temp)[2]     = ui.preheat_bed_temp;
+          uint8_t (&ui_preheat_fan_speed)[2]    = ui.preheat_fan_speed;
+        #else
+          int16_t ui_preheat_hotend_temp[2], ui_preheat_bed_temp[2];
+          uint8_t ui_preheat_fan_speed[2];
         #endif
-        EEPROM_READ(lcd_preheat_hotend_temp); // 2 floats
-        EEPROM_READ(lcd_preheat_bed_temp);    // 2 floats
-        EEPROM_READ(lcd_preheat_fan_speed);   // 2 floats
+        EEPROM_READ(ui_preheat_hotend_temp); // 2 floats
+        EEPROM_READ(ui_preheat_bed_temp);    // 2 floats
+        EEPROM_READ(ui_preheat_fan_speed);   // 2 floats
       }
 
       //
@@ -1366,10 +1379,12 @@ void MarlinSettings::postprocess() {
       //
       {
         _FIELD_TEST(lcd_contrast);
-        #if !HAS_LCD_CONTRAST
-          int16_t lcd_contrast;
-        #endif
+
+        int16_t lcd_contrast;
         EEPROM_READ(lcd_contrast);
+        #if HAS_LCD_CONTRAST
+          ui.set_contrast(lcd_contrast);
+        #endif
       }
 
       //
@@ -2028,12 +2043,12 @@ void MarlinSettings::reset(PORTARG_SOLO) {
   #endif
 
   #if HAS_LCD_MENU
-    lcd_preheat_hotend_temp[0] = PREHEAT_1_TEMP_HOTEND;
-    lcd_preheat_hotend_temp[1] = PREHEAT_2_TEMP_HOTEND;
-    lcd_preheat_bed_temp[0] = PREHEAT_1_TEMP_BED;
-    lcd_preheat_bed_temp[1] = PREHEAT_2_TEMP_BED;
-    lcd_preheat_fan_speed[0] = PREHEAT_1_FAN_SPEED;
-    lcd_preheat_fan_speed[1] = PREHEAT_2_FAN_SPEED;
+    ui.preheat_hotend_temp[0] = PREHEAT_1_TEMP_HOTEND;
+    ui.preheat_hotend_temp[1] = PREHEAT_2_TEMP_HOTEND;
+    ui.preheat_bed_temp[0] = PREHEAT_1_TEMP_BED;
+    ui.preheat_bed_temp[1] = PREHEAT_2_TEMP_BED;
+    ui.preheat_fan_speed[0] = PREHEAT_1_FAN_SPEED;
+    ui.preheat_fan_speed[1] = PREHEAT_2_FAN_SPEED;
   #endif
 
   #if ENABLED(PIDTEMP)
@@ -2057,7 +2072,7 @@ void MarlinSettings::reset(PORTARG_SOLO) {
   #endif
 
   #if HAS_LCD_CONTRAST
-    lcd_contrast = DEFAULT_LCD_CONTRAST;
+    ui.set_contrast(DEFAULT_LCD_CONTRAST);
   #endif
 
   #if ENABLED(FWRETRACT)
@@ -2561,12 +2576,12 @@ void MarlinSettings::reset(PORTARG_SOLO) {
         CONFIG_ECHO_START;
         SERIAL_ECHOLNPGM_P(port, "Material heatup parameters:");
       }
-      for (uint8_t i = 0; i < COUNT(lcd_preheat_hotend_temp); i++) {
+      for (uint8_t i = 0; i < COUNT(ui.preheat_hotend_temp); i++) {
         CONFIG_ECHO_START;
         SERIAL_ECHOPAIR_P(port, "  M145 S", (int)i);
-        SERIAL_ECHOPAIR_P(port, " H", TEMP_UNIT(lcd_preheat_hotend_temp[i]));
-        SERIAL_ECHOPAIR_P(port, " B", TEMP_UNIT(lcd_preheat_bed_temp[i]));
-        SERIAL_ECHOLNPAIR_P(port, " F", int(lcd_preheat_fan_speed[i]));
+        SERIAL_ECHOPAIR_P(port, " H", TEMP_UNIT(ui.preheat_hotend_temp[i]));
+        SERIAL_ECHOPAIR_P(port, " B", TEMP_UNIT(ui.preheat_bed_temp[i]));
+        SERIAL_ECHOLNPAIR_P(port, " F", int(ui.preheat_fan_speed[i]));
       }
 
     #endif
@@ -2625,7 +2640,7 @@ void MarlinSettings::reset(PORTARG_SOLO) {
         SERIAL_ECHOLNPGM_P(port, "LCD Contrast:");
       }
       CONFIG_ECHO_START;
-      SERIAL_ECHOLNPAIR_P(port, "  M250 C", lcd_contrast);
+      SERIAL_ECHOLNPAIR_P(port, "  M250 C", ui.contrast);
     #endif
 
     #if ENABLED(FWRETRACT)
diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp
index 28e0d92f69..1e2d805b6e 100644
--- a/Marlin/src/module/endstops.cpp
+++ b/Marlin/src/module/endstops.cpp
@@ -337,7 +337,7 @@ void Endstops::event_handler() {
     SERIAL_EOL();
 
     #if ENABLED(ULTRA_LCD)
-      lcd_status_printf_P(0, PSTR(MSG_LCD_ENDSTOPS " %c %c %c %c"), chrX, chrY, chrZ, chrP);
+      ui.status_printf_P(0, PSTR(MSG_LCD_ENDSTOPS " %c %c %c %c"), chrX, chrY, chrZ, chrP);
     #endif
 
     #if ENABLED(ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED) && ENABLED(SDSUPPORT)
diff --git a/Marlin/src/module/motion.cpp b/Marlin/src/module/motion.cpp
index 9cd83907c1..ec888f12b5 100644
--- a/Marlin/src/module/motion.cpp
+++ b/Marlin/src/module/motion.cpp
@@ -1026,7 +1026,7 @@ void prepare_move_to_destination() {
       SERIAL_ECHOLNPGM(" " MSG_FIRST);
 
       #if ENABLED(ULTRA_LCD)
-        lcd_status_printf_P(0, PSTR(MSG_HOME " %s%s%s " MSG_FIRST), xx ? MSG_X : "", yy ? MSG_Y : "", zz ? MSG_Z : "");
+        ui.status_printf_P(0, PSTR(MSG_HOME " %s%s%s " MSG_FIRST), xx ? MSG_X : "", yy ? MSG_Y : "", zz ? MSG_Z : "");
       #endif
       return true;
     }
@@ -1121,7 +1121,7 @@ void do_homing_move(const AxisEnum axis, const float distance, const float fr_mm
       serialprintPGM(msg_wait_for_bed_heating);
       LCD_MESSAGEPGM(MSG_BED_HEATING);
       while (thermalManager.isHeatingBed()) safe_delay(200);
-      lcd_reset_status();
+      ui.reset_status();
     }
   #endif
 
diff --git a/Marlin/src/module/probe.cpp b/Marlin/src/module/probe.cpp
index bec70918a6..5597714416 100644
--- a/Marlin/src/module/probe.cpp
+++ b/Marlin/src/module/probe.cpp
@@ -370,15 +370,15 @@ FORCE_INLINE void probe_specific_action(const bool deploy) {
     BUZZ(100, 698);
 
     PGM_P const ds_str = deploy ? PSTR(MSG_MANUAL_DEPLOY) : PSTR(MSG_MANUAL_STOW);
-    lcd_return_to_status();       // To display the new status message
-    lcd_setstatusPGM(ds_str, 99);
+    ui.return_to_status();       // To display the new status message
+    ui.setstatusPGM(ds_str, 99);
     serialprintPGM(ds_str);
     SERIAL_EOL();
 
     KEEPALIVE_STATE(PAUSED_FOR_USER);
     wait_for_user = true;
     while (wait_for_user) idle();
-    lcd_reset_status();
+    ui.reset_status();
     KEEPALIVE_STATE(IN_HANDLER);
 
   #endif // PAUSE_BEFORE_DEPLOY_STOW
@@ -527,7 +527,7 @@ static bool do_probe_move(const float z, const float fr_mm_s) {
       serialprintPGM(msg_wait_for_bed_heating);
       LCD_MESSAGEPGM(MSG_BED_HEATING);
       while (thermalManager.isHeatingBed()) safe_delay(200);
-      lcd_reset_status();
+      ui.reset_status();
     }
   #endif
 
diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp
index 4750b7dcd6..77ac6f7a73 100644
--- a/Marlin/src/module/temperature.cpp
+++ b/Marlin/src/module/temperature.cpp
@@ -498,7 +498,7 @@ uint8_t Temperature::soft_pwm_amount[HOTENDS];
 
         return;
       }
-      lcd_update();
+      ui.update();
     }
     disable_all_heaters();
     #if ENABLED(PRINTER_EVENT_LEDS)
@@ -2123,7 +2123,7 @@ void Temperature::isr() {
   // Update lcd buttons 488 times per second
   //
   static bool do_buttons;
-  if ((do_buttons ^= true)) lcd_buttons_update();
+  if ((do_buttons ^= true)) ui.update_buttons();
 
   /**
    * One sensor is sampled on every other call of the ISR.
@@ -2425,9 +2425,9 @@ void Temperature::isr() {
     void Temperature::set_heating_message(const uint8_t e) {
       const bool heating = isHeatingHotend(e);
       #if HOTENDS > 1
-        lcd_status_printf_P(0, heating ? PSTR("E%i " MSG_HEATING) : PSTR("E%i " MSG_COOLING), int(e + 1));
+        ui.status_printf_P(0, heating ? PSTR("E%i " MSG_HEATING) : PSTR("E%i " MSG_COOLING), int(e + 1));
       #else
-        lcd_setstatusPGM(heating ? PSTR("E " MSG_HEATING) : PSTR("E " MSG_COOLING));
+        ui.setstatusPGM(heating ? PSTR("E " MSG_HEATING) : PSTR("E " MSG_COOLING));
       #endif
     }
   #endif
@@ -2530,16 +2530,16 @@ void Temperature::isr() {
         }
 
         #if G26_CLICK_CAN_CANCEL
-          if (click_to_cancel && use_click()) {
+          if (click_to_cancel && ui.use_click()) {
             wait_for_heatup = false;
-            lcd_quick_feedback();
+            ui.quick_feedback();
           }
         #endif
 
       } while (wait_for_heatup && TEMP_CONDITIONS);
 
       if (wait_for_heatup) {
-        lcd_reset_status();
+        ui.reset_status();
         #if ENABLED(PRINTER_EVENT_LEDS)
           printerEventLEDs.onHeatingDone();
         #endif
@@ -2655,15 +2655,15 @@ void Temperature::isr() {
         }
 
         #if G26_CLICK_CAN_CANCEL
-          if (click_to_cancel && use_click()) {
+          if (click_to_cancel && ui.use_click()) {
             wait_for_heatup = false;
-            lcd_quick_feedback();
+            ui.quick_feedback();
           }
         #endif
 
       } while (wait_for_heatup && TEMP_BED_CONDITIONS);
 
-      if (wait_for_heatup) lcd_reset_status();
+      if (wait_for_heatup) ui.reset_status();
 
       #if DISABLED(BUSY_WHILE_HEATING) && ENABLED(HOST_KEEPALIVE_FEATURE)
         gcode.busy_state = old_busy_state;
diff --git a/Marlin/src/module/tool_change.cpp b/Marlin/src/module/tool_change.cpp
index 1d255a88fb..274a64ee47 100644
--- a/Marlin/src/module/tool_change.cpp
+++ b/Marlin/src/module/tool_change.cpp
@@ -540,7 +540,7 @@ void tool_change(const uint8_t tmp_extruder, const float fr_mm_s/*=0.0*/, bool n
     }
 
     #if HAS_LCD_MENU
-      lcd_return_to_status();
+      ui.return_to_status();
     #endif
 
     #if ENABLED(TOOLCHANGE_FILAMENT_SWAP)
diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp
index c221475c1f..9c86f48fae 100644
--- a/Marlin/src/sd/cardreader.cpp
+++ b/Marlin/src/sd/cardreader.cpp
@@ -446,7 +446,7 @@ void CardReader::openFile(char * const path, const bool read, const bool subcall
       SERIAL_PROTOCOLLNPGM(MSG_SD_FILE_SELECTED);
 
       getfilename(0, fname);
-      lcd_setstatus(longFilename[0] ? longFilename : fname);
+      ui.setstatus(longFilename[0] ? longFilename : fname);
       //if (longFilename[0]) {
       //  SERIAL_PROTOCOLPAIR(MSG_SD_FILE_LONG_NAME, longFilename);
       //}
@@ -470,7 +470,7 @@ void CardReader::openFile(char * const path, const bool read, const bool subcall
         emergency_parser.disable();
       #endif
       SERIAL_PROTOCOLLNPAIR(MSG_SD_WRITE_TO_FILE, fname);
-      lcd_setstatus(fname);
+      ui.setstatus(fname);
     }
   }
 }
@@ -963,10 +963,10 @@ void CardReader::printingHasFinished() {
       presort();
     #endif
     #if ENABLED(ULTRA_LCD) && ENABLED(LCD_SET_PROGRESS_MANUALLY)
-      progress_bar_percent = 0;
+      ui.progress_bar_percent = 0;
     #endif
     #if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
-      lcd_reselect_last_file();
+      ui.reselect_last_file();
     #endif
   }
 }
-- 
GitLab