aboutsummaryrefslogtreecommitdiff
path: root/Marlin/src/lcd/menu
diff options
context:
space:
mode:
authorGeorgiy Bondarenko <69736697+nehilo@users.noreply.github.com>2021-03-04 20:54:23 +0300
committerGeorgiy Bondarenko <69736697+nehilo@users.noreply.github.com>2021-03-04 20:54:23 +0300
commite8701195e66f2d27ffe17fb514eae8173795aaf7 (patch)
tree9f519c4abf6556b9ae7190a6210d87ead1dfadde /Marlin/src/lcd/menu
downloadkp3s-lgvl-e8701195e66f2d27ffe17fb514eae8173795aaf7.tar.xz
kp3s-lgvl-e8701195e66f2d27ffe17fb514eae8173795aaf7.zip
Initial commit
Diffstat (limited to 'Marlin/src/lcd/menu')
-rw-r--r--Marlin/src/lcd/menu/game/brickout.cpp207
-rw-r--r--Marlin/src/lcd/menu/game/brickout.h38
-rw-r--r--Marlin/src/lcd/menu/game/game.cpp66
-rw-r--r--Marlin/src/lcd/menu/game/game.h62
-rw-r--r--Marlin/src/lcd/menu/game/invaders.cpp438
-rw-r--r--Marlin/src/lcd/menu/game/invaders.h62
-rw-r--r--Marlin/src/lcd/menu/game/maze.cpp134
-rw-r--r--Marlin/src/lcd/menu/game/maze.h30
-rw-r--r--Marlin/src/lcd/menu/game/snake.cpp323
-rw-r--r--Marlin/src/lcd/menu/game/snake.h38
-rw-r--r--Marlin/src/lcd/menu/game/types.h46
-rw-r--r--Marlin/src/lcd/menu/menu.cpp385
-rw-r--r--Marlin/src/lcd/menu/menu.h256
-rw-r--r--Marlin/src/lcd/menu/menu_addon.h33
-rw-r--r--Marlin/src/lcd/menu/menu_advanced.cpp630
-rw-r--r--Marlin/src/lcd/menu/menu_backlash.cpp53
-rw-r--r--Marlin/src/lcd/menu/menu_bed_corners.cpp361
-rw-r--r--Marlin/src/lcd/menu/menu_bed_leveling.cpp301
-rw-r--r--Marlin/src/lcd/menu/menu_cancelobject.cpp74
-rw-r--r--Marlin/src/lcd/menu/menu_configuration.cpp436
-rw-r--r--Marlin/src/lcd/menu/menu_custom.cpp129
-rw-r--r--Marlin/src/lcd/menu/menu_delta_calibrate.cpp150
-rw-r--r--Marlin/src/lcd/menu/menu_filament.cpp336
-rw-r--r--Marlin/src/lcd/menu/menu_game.cpp48
-rw-r--r--Marlin/src/lcd/menu/menu_info.cpp306
-rw-r--r--Marlin/src/lcd/menu/menu_item.h495
-rw-r--r--Marlin/src/lcd/menu/menu_job_recovery.cpp57
-rw-r--r--Marlin/src/lcd/menu/menu_language.cpp59
-rw-r--r--Marlin/src/lcd/menu/menu_led.cpp159
-rw-r--r--Marlin/src/lcd/menu/menu_main.cpp337
-rw-r--r--Marlin/src/lcd/menu/menu_media.cpp141
-rw-r--r--Marlin/src/lcd/menu/menu_mixer.cpp278
-rw-r--r--Marlin/src/lcd/menu/menu_mmu2.cpp170
-rw-r--r--Marlin/src/lcd/menu/menu_mmu2.h28
-rw-r--r--Marlin/src/lcd/menu/menu_motion.cpp415
-rw-r--r--Marlin/src/lcd/menu/menu_password.cpp182
-rw-r--r--Marlin/src/lcd/menu/menu_power_monitor.cpp62
-rw-r--r--Marlin/src/lcd/menu/menu_probe_offset.cpp190
-rw-r--r--Marlin/src/lcd/menu/menu_spindle_laser.cpp74
-rw-r--r--Marlin/src/lcd/menu/menu_temperature.cpp252
-rw-r--r--Marlin/src/lcd/menu/menu_tmc.cpp264
-rw-r--r--Marlin/src/lcd/menu/menu_touch_screen.cpp36
-rw-r--r--Marlin/src/lcd/menu/menu_tramming.cpp104
-rw-r--r--Marlin/src/lcd/menu/menu_tune.cpp239
-rw-r--r--Marlin/src/lcd/menu/menu_ubl.cpp639
45 files changed, 9123 insertions, 0 deletions
diff --git a/Marlin/src/lcd/menu/game/brickout.cpp b/Marlin/src/lcd/menu/game/brickout.cpp
new file mode 100644
index 0000000..4bdc924
--- /dev/null
+++ b/Marlin/src/lcd/menu/game/brickout.cpp
@@ -0,0 +1,207 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(MARLIN_BRICKOUT)
+
+#include "game.h"
+
+#define BRICK_H 5
+#define BRICK_TOP MENU_FONT_ASCENT
+
+#define PADDLE_H 2
+#define PADDLE_VEL 3
+#define PADDLE_W ((LCD_PIXEL_WIDTH) / 8)
+#define PADDLE_Y (LCD_PIXEL_HEIGHT - 1 - PADDLE_H)
+
+#define BRICK_W ((LCD_PIXEL_WIDTH) / (BRICK_COLS))
+#define BRICK_BOT (BRICK_TOP + BRICK_H * BRICK_ROWS - 1)
+
+#define BRICK_COL(X) ((X) / (BRICK_W))
+#define BRICK_ROW(Y) ((Y - (BRICK_TOP)) / (BRICK_H))
+
+brickout_data_t &bdat = marlin_game_data.brickout;
+
+inline void reset_bricks(const uint16_t v) {
+ bdat.brick_count = (BRICK_COLS) * (BRICK_ROWS);
+ LOOP_L_N(i, BRICK_ROWS) bdat.bricks[i] = v;
+}
+
+void reset_ball() {
+ constexpr uint8_t ball_dist = 24;
+ bdat.bally = BTOF(PADDLE_Y - ball_dist);
+ bdat.ballv = FTOP(1.3f);
+ bdat.ballh = -FTOP(1.25f);
+ uint8_t bx = bdat.paddle_x + (PADDLE_W) / 2 + ball_dist;
+ if (bx >= LCD_PIXEL_WIDTH - 10) { bx -= ball_dist * 2; bdat.ballh = -bdat.ballh; }
+ bdat.ballx = BTOF(bx);
+ bdat.hit_dir = -1;
+}
+
+void BrickoutGame::game_screen() {
+ if (game_frame()) { // Run logic twice for finer resolution
+ // Update Paddle Position
+ bdat.paddle_x = constrain(int8_t(ui.encoderPosition), 0, (LCD_PIXEL_WIDTH - (PADDLE_W)) / (PADDLE_VEL));
+ ui.encoderPosition = bdat.paddle_x;
+ bdat.paddle_x *= (PADDLE_VEL);
+
+ // Run the ball logic
+ if (game_state) do {
+
+ // Provisionally update the ball position
+ const fixed_t newx = bdat.ballx + bdat.ballh, newy = bdat.bally + bdat.ballv; // current next position
+ if (!WITHIN(newx, 0, BTOF(LCD_PIXEL_WIDTH - 1))) { // out in x?
+ bdat.ballh = -bdat.ballh; _BUZZ(5, 220); // bounce x
+ }
+ if (newy < 0) { // out in y?
+ bdat.ballv = -bdat.ballv; _BUZZ(5, 280); // bounce v
+ bdat.hit_dir = 1;
+ }
+ // Did the ball go below the bottom?
+ else if (newy > BTOF(LCD_PIXEL_HEIGHT)) {
+ _BUZZ(500, 75);
+ if (--bdat.balls_left) reset_ball(); else game_state = 0;
+ break; // done
+ }
+
+ // Is the ball colliding with a brick?
+ if (WITHIN(newy, BTOF(BRICK_TOP), BTOF(BRICK_BOT))) {
+ const int8_t bit = BRICK_COL(FTOB(newx)), row = BRICK_ROW(FTOB(newy));
+ const uint16_t mask = _BV(bit);
+ if (bdat.bricks[row] & mask) {
+ // Yes. Remove it!
+ bdat.bricks[row] &= ~mask;
+ // Score!
+ score += BRICK_ROWS - row;
+ // If bricks are gone, go to reset state
+ if (!--bdat.brick_count) game_state = 2;
+ // Bounce the ball cleverly
+ if ((bdat.ballv < 0) == (bdat.hit_dir < 0)) { bdat.ballv = -bdat.ballv; bdat.ballh += fixed_t(random(-16, 16)); _BUZZ(5, 880); }
+ else { bdat.ballh = -bdat.ballh; bdat.ballv += fixed_t(random(-16, 16)); _BUZZ(5, 640); }
+ }
+ }
+ // Is the ball moving down and in paddle range?
+ else if (bdat.ballv > 0 && WITHIN(newy, BTOF(PADDLE_Y), BTOF(PADDLE_Y + PADDLE_H))) {
+ // Ball actually hitting paddle
+ const int8_t diff = FTOB(newx) - bdat.paddle_x;
+ if (WITHIN(diff, 0, PADDLE_W - 1)) {
+
+ // Reverse Y direction
+ bdat.ballv = -bdat.ballv; _BUZZ(3, 880);
+ bdat.hit_dir = -1;
+
+ // Near edges affects X velocity
+ const bool is_left_edge = (diff <= 1);
+ if (is_left_edge || diff >= PADDLE_W-1 - 1) {
+ if ((bdat.ballh > 0) == is_left_edge) bdat.ballh = -bdat.ballh;
+ }
+ else if (diff <= 3) {
+ bdat.ballh += fixed_t(random(-64, 0));
+ NOLESS(bdat.ballh, BTOF(-2));
+ NOMORE(bdat.ballh, BTOF(2));
+ }
+ else if (diff >= PADDLE_W-1 - 3) {
+ bdat.ballh += fixed_t(random( 0, 64));
+ NOLESS(bdat.ballh, BTOF(-2));
+ NOMORE(bdat.ballh, BTOF(2));
+ }
+
+ // Paddle hit after clearing the board? Reset the board.
+ if (game_state == 2) { reset_bricks(0xFFFF); game_state = 1; }
+ }
+ }
+
+ bdat.ballx += bdat.ballh; bdat.bally += bdat.ballv; // update with new velocity
+
+ } while (false);
+ }
+
+ u8g.setColorIndex(1);
+
+ // Draw bricks
+ if (PAGE_CONTAINS(BRICK_TOP, BRICK_BOT)) {
+ LOOP_L_N(y, BRICK_ROWS) {
+ const uint8_t yy = y * BRICK_H + BRICK_TOP;
+ if (PAGE_CONTAINS(yy, yy + BRICK_H - 1)) {
+ LOOP_L_N(x, BRICK_COLS) {
+ if (TEST(bdat.bricks[y], x)) {
+ const uint8_t xx = x * BRICK_W;
+ LOOP_L_N(v, BRICK_H - 1)
+ if (PAGE_CONTAINS(yy + v, yy + v))
+ u8g.drawHLine(xx, yy + v, BRICK_W - 1);
+ }
+ }
+ }
+ }
+ }
+
+ // Draw paddle
+ if (PAGE_CONTAINS(PADDLE_Y-1, PADDLE_Y)) {
+ u8g.drawHLine(bdat.paddle_x, PADDLE_Y, PADDLE_W);
+ #if PADDLE_H > 1
+ u8g.drawHLine(bdat.paddle_x, PADDLE_Y-1, PADDLE_W);
+ #if PADDLE_H > 2
+ u8g.drawHLine(bdat.paddle_x, PADDLE_Y-2, PADDLE_W);
+ #endif
+ #endif
+ }
+
+ // Draw ball while game is running
+ if (game_state) {
+ const uint8_t by = FTOB(bdat.bally);
+ if (PAGE_CONTAINS(by, by+1))
+ u8g.drawFrame(FTOB(bdat.ballx), by, 2, 2);
+ }
+ // Or draw GAME OVER
+ else
+ draw_game_over();
+
+ if (PAGE_UNDER(MENU_FONT_ASCENT)) {
+ // Score Digits
+ //const uint8_t sx = (LCD_PIXEL_WIDTH - (score >= 10 ? score >= 100 ? score >= 1000 ? 4 : 3 : 2 : 1) * MENU_FONT_WIDTH) / 2;
+ constexpr uint8_t sx = 0;
+ lcd_put_int(sx, MENU_FONT_ASCENT - 1, score);
+
+ // Balls Left
+ lcd_moveto(LCD_PIXEL_WIDTH - MENU_FONT_WIDTH * 3, MENU_FONT_ASCENT - 1);
+ PGM_P const ohs = PSTR("ooo\0\0");
+ lcd_put_u8str_P(ohs + 3 - bdat.balls_left);
+ }
+
+ // A click always exits this game
+ if (ui.use_click()) exit_game();
+}
+
+#define SCREEN_M ((LCD_PIXEL_WIDTH) / 2)
+
+void BrickoutGame::enter_game() {
+ init_game(2, game_screen); // 2 = reset bricks on paddle hit
+ constexpr uint8_t paddle_start = SCREEN_M - (PADDLE_W) / 2;
+ bdat.paddle_x = paddle_start;
+ bdat.balls_left = 3;
+ reset_bricks(0x0000);
+ reset_ball();
+ ui.encoderPosition = paddle_start / (PADDLE_VEL);
+}
+
+#endif // MARLIN_BRICKOUT
diff --git a/Marlin/src/lcd/menu/game/brickout.h b/Marlin/src/lcd/menu/game/brickout.h
new file mode 100644
index 0000000..cf1fbc6
--- /dev/null
+++ b/Marlin/src/lcd/menu/game/brickout.h
@@ -0,0 +1,38 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "types.h"
+
+#define BRICK_ROWS 4
+#define BRICK_COLS 16
+
+typedef struct {
+ uint8_t balls_left, brick_count;
+ uint16_t bricks[BRICK_ROWS];
+ int8_t paddle_x, hit_dir;
+ fixed_t ballx, bally, ballh, ballv;
+} brickout_data_t;
+
+class BrickoutGame : MarlinGame { public: static void enter_game(), game_screen(); };
+
+extern BrickoutGame brickout;
diff --git a/Marlin/src/lcd/menu/game/game.cpp b/Marlin/src/lcd/menu/game/game.cpp
new file mode 100644
index 0000000..c14bd2a
--- /dev/null
+++ b/Marlin/src/lcd/menu/game/game.cpp
@@ -0,0 +1,66 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if HAS_GAMES
+
+#include "game.h"
+
+int MarlinGame::score;
+uint8_t MarlinGame::game_state;
+millis_t MarlinGame::next_frame;
+
+MarlinGameData marlin_game_data;
+
+bool MarlinGame::game_frame() {
+ static int8_t slew;
+ if (ui.first_page) slew = 2;
+ ui.refresh(LCDVIEW_CALL_NO_REDRAW); // Refresh as often as possible
+ return (game_state && slew-- > 0);
+}
+
+void MarlinGame::draw_game_over() {
+ constexpr int8_t gowide = (MENU_FONT_WIDTH) * 9,
+ gohigh = MENU_FONT_ASCENT - 3,
+ lx = (LCD_PIXEL_WIDTH - gowide) / 2,
+ ly = (LCD_PIXEL_HEIGHT + gohigh) / 2;
+ if (PAGE_CONTAINS(ly - gohigh - 1, ly + 1)) {
+ u8g.setColorIndex(0);
+ u8g.drawBox(lx - 1, ly - gohigh - 1, gowide + 2, gohigh + 2);
+ u8g.setColorIndex(1);
+ if (ui.get_blink()) lcd_put_u8str_P(lx, ly, PSTR("GAME OVER"));
+ }
+}
+
+void MarlinGame::init_game(const uint8_t init_state, const screenFunc_t screen) {
+ score = 0;
+ game_state = init_state;
+ ui.goto_screen(screen);
+ ui.defer_status_screen();
+}
+
+void MarlinGame::exit_game() {
+ ui.goto_previous_screen_no_defer();
+}
+
+#endif // HAS_GAMES
diff --git a/Marlin/src/lcd/menu/game/game.h b/Marlin/src/lcd/menu/game/game.h
new file mode 100644
index 0000000..cba79e4
--- /dev/null
+++ b/Marlin/src/lcd/menu/game/game.h
@@ -0,0 +1,62 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "../../../inc/MarlinConfigPre.h"
+#include "../../dogm/marlinui_DOGM.h"
+#include "../../lcdprint.h"
+#include "../../marlinui.h"
+
+//#define MUTE_GAMES
+
+#if ENABLED(MUTE_GAMES) || !HAS_BUZZER
+ #define _BUZZ(D,F) NOOP
+#else
+ #define _BUZZ(D,F) BUZZ(D,F)
+#endif
+
+#if HAS_GAME_MENU
+ void menu_game();
+#endif
+
+#if ENABLED(MARLIN_BRICKOUT)
+ #include "brickout.h"
+#endif
+#if ENABLED(MARLIN_INVADERS)
+ #include "invaders.h"
+#endif
+#if ENABLED(MARLIN_MAZE)
+ #include "maze.h"
+#endif
+#if ENABLED(MARLIN_SNAKE)
+ #include "snake.h"
+#endif
+
+// Pool game data to save SRAM
+union MarlinGameData {
+ TERN_(MARLIN_BRICKOUT, brickout_data_t brickout);
+ TERN_(MARLIN_INVADERS, invaders_data_t invaders);
+ TERN_(MARLIN_SNAKE, snake_data_t snake);
+ TERN_(MARLIN_MAZE, maze_data_t maze);
+};
+
+extern MarlinGameData marlin_game_data;
diff --git a/Marlin/src/lcd/menu/game/invaders.cpp b/Marlin/src/lcd/menu/game/invaders.cpp
new file mode 100644
index 0000000..56e4c22
--- /dev/null
+++ b/Marlin/src/lcd/menu/game/invaders.cpp
@@ -0,0 +1,438 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(MARLIN_INVADERS)
+
+#include "game.h"
+
+#define CANNON_W 11
+#define CANNON_H 8
+#define CANNON_VEL 4
+#define CANNON_Y (LCD_PIXEL_HEIGHT - 1 - CANNON_H)
+
+#define INVADER_VEL 3
+
+#define INVADER_TOP MENU_FONT_ASCENT
+#define INVADERS_WIDE ((INVADER_COL_W) * (INVADER_COLS))
+#define INVADERS_HIGH ((INVADER_ROW_H) * (INVADER_ROWS))
+
+#define UFO_H 5
+#define UFO_W 13
+
+#define LASER_H 4
+#define SHOT_H 3
+#define EXPL_W 11
+#define LIFE_W 8
+#define LIFE_H 5
+
+#define INVADER_RIGHT ((INVADER_COLS) * (INVADER_COL_W))
+
+// 11x8
+const unsigned char invader[3][2][16] PROGMEM = {
+ { { B00000110,B00000000,
+ B00001111,B00000000,
+ B00011111,B10000000,
+ B00110110,B11000000,
+ B00111111,B11000000,
+ B00001001,B00000000,
+ B00010110,B10000000,
+ B00101001,B01000000
+ }, {
+ B00000110,B00000000,
+ B00001111,B00000000,
+ B00011111,B10000000,
+ B00110110,B11000000,
+ B00111111,B11000000,
+ B00010110,B10000000,
+ B00100000,B01000000,
+ B00010000,B10000000
+ }
+ }, {
+ { B00010000,B01000000,
+ B00001000,B10000000,
+ B00011111,B11000000,
+ B00110111,B01100000,
+ B01111111,B11110000,
+ B01011111,B11010000,
+ B01010000,B01010000,
+ B00001101,B10000000
+ }, {
+ B00010000,B01000000,
+ B01001000,B10010000,
+ B01011111,B11010000,
+ B01110111,B01110000,
+ B01111111,B11110000,
+ B00011111,B11000000,
+ B00010000,B01000000,
+ B00100000,B00100000
+ }
+ }, {
+ { B00001111,B00000000,
+ B01111111,B11100000,
+ B11111111,B11110000,
+ B11100110,B01110000,
+ B11111111,B11110000,
+ B00011001,B10000000,
+ B00110110,B11000000,
+ B11000000,B00110000
+ }, {
+ B00001111,B00000000,
+ B01111111,B11100000,
+ B11111111,B11110000,
+ B11100110,B01110000,
+ B11111111,B11110000,
+ B00011001,B10000000,
+ B00110110,B11000000,
+ B00011001,B10000000
+ }
+ }
+};
+const unsigned char cannon[] PROGMEM = {
+ B00000100,B00000000,
+ B00001110,B00000000,
+ B00001110,B00000000,
+ B01111111,B11000000,
+ B11111111,B11100000,
+ B11111111,B11100000,
+ B11111111,B11100000,
+ B11111111,B11100000
+};
+const unsigned char life[] PROGMEM = {
+ B00010000,
+ B01111100,
+ B11111110,
+ B11111110,
+ B11111110
+};
+const unsigned char explosion[] PROGMEM = {
+ B01000100,B01000000,
+ B00100100,B10000000,
+ B00000000,B00000000,
+ B00110001,B10000000,
+ B00000000,B00000000,
+ B00100100,B10000000,
+ B01000100,B01000000
+};
+const unsigned char ufo[] PROGMEM = {
+ B00011111,B11000000,
+ B01111111,B11110000,
+ B11011101,B11011000,
+ B11111111,B11111000,
+ B01111111,B11110000
+};
+
+constexpr uint8_t inv_type[] = {
+ #if INVADER_ROWS == 5
+ 0, 1, 1, 2, 2
+ #elif INVADER_ROWS == 4
+ 0, 1, 1, 2
+ #elif INVADER_ROWS == 3
+ 0, 1, 2
+ #else
+ #error "INVASION_SIZE must be 3, 4, or 5."
+ #endif
+};
+
+invaders_data_t &idat = marlin_game_data.invaders;
+
+#define INV_X_LEFT(C,T) (idat.pos.x + (C) * (INVADER_COL_W) + inv_off[T])
+#define INV_X_CTR(C,T) (INV_X_LEFT(C,T) + inv_wide[T] / 2)
+#define INV_Y_BOT(R) (idat.pos.y + (R + 1) * (INVADER_ROW_H) - 2)
+
+constexpr uint8_t inv_off[] = { 2, 1, 0 }, inv_wide[] = { 8, 11, 12 };
+
+inline void update_invader_data() {
+ uint8_t inv_mask = 0;
+ // Get a list of all active invaders
+ uint8_t sc = 0;
+ LOOP_L_N(y, INVADER_ROWS) {
+ uint8_t m = idat.bugs[y];
+ if (m) idat.botmost = y + 1;
+ inv_mask |= m;
+ LOOP_L_N(x, INVADER_COLS)
+ if (TEST(m, x)) idat.shooters[sc++] = (y << 4) | x;
+ }
+ idat.leftmost = 0;
+ LOOP_L_N(i, INVADER_COLS) { if (TEST(inv_mask, i)) break; idat.leftmost -= INVADER_COL_W; }
+ idat.rightmost = LCD_PIXEL_WIDTH - (INVADERS_WIDE);
+ for (uint8_t i = INVADER_COLS; i--;) { if (TEST(inv_mask, i)) break; idat.rightmost += INVADER_COL_W; }
+ if (idat.count == 2) idat.dir = idat.dir > 0 ? INVADER_VEL + 1 : -(INVADER_VEL + 1);
+}
+
+inline void reset_bullets() {
+ LOOP_L_N(i, COUNT(idat.bullet)) idat.bullet[i].v = 0;
+}
+
+inline void reset_invaders() {
+ idat.pos.x = 0; idat.pos.y = INVADER_TOP;
+ idat.dir = INVADER_VEL;
+ idat.count = (INVADER_COLS) * (INVADER_ROWS);
+ LOOP_L_N(i, INVADER_ROWS) idat.bugs[i] = _BV(INVADER_COLS) - 1;
+ update_invader_data();
+ reset_bullets();
+}
+
+
+inline void spawn_ufo() {
+ idat.ufov = random(0, 2) ? 1 : -1;
+ idat.ufox = idat.ufov > 0 ? -(UFO_W) : LCD_PIXEL_WIDTH - 1;
+}
+
+inline void reset_player() {
+ idat.cannon_x = 0;
+ ui.encoderPosition = 0;
+}
+
+inline void fire_cannon() {
+ idat.laser.x = idat.cannon_x + CANNON_W / 2;
+ idat.laser.y = LCD_PIXEL_HEIGHT - CANNON_H - (LASER_H);
+ idat.laser.v = -(LASER_H);
+}
+
+inline void explode(const int8_t x, const int8_t y, const int8_t v=4) {
+ idat.explod.x = x - (EXPL_W) / 2;
+ idat.explod.y = y;
+ idat.explod.v = v;
+}
+
+inline void kill_cannon(uint8_t &game_state, const uint8_t st) {
+ reset_bullets();
+ explode(idat.cannon_x + (CANNON_W) / 2, CANNON_Y, 6);
+ _BUZZ(1000, 10);
+ if (--idat.cannons_left) {
+ idat.laser.v = 0;
+ game_state = st;
+ reset_player();
+ }
+ else
+ game_state = 0;
+}
+
+void InvadersGame::game_screen() {
+ ui.refresh(LCDVIEW_CALL_NO_REDRAW); // Call as often as possible
+
+ // Run game logic once per full screen
+ if (ui.first_page) {
+
+ // Update Cannon Position
+ int16_t ep = constrain(int16_t(ui.encoderPosition), 0, (LCD_PIXEL_WIDTH - (CANNON_W)) / (CANNON_VEL));
+ ui.encoderPosition = ep;
+
+ ep *= (CANNON_VEL);
+ if (ep > idat.cannon_x) { idat.cannon_x += CANNON_VEL - 1; if (ep - idat.cannon_x < 2) idat.cannon_x = ep; }
+ if (ep < idat.cannon_x) { idat.cannon_x -= CANNON_VEL - 1; if (idat.cannon_x - ep < 2) idat.cannon_x = ep; }
+
+ // Run the game logic
+ if (game_state) do {
+
+ // Move the UFO, if any
+ if (idat.ufov) { idat.ufox += idat.ufov; if (!WITHIN(idat.ufox, -(UFO_W), LCD_PIXEL_WIDTH - 1)) idat.ufov = 0; }
+
+ if (game_state > 1) { if (--game_state == 2) { reset_invaders(); } else if (game_state == 100) { game_state = 1; } break; }
+
+ const bool did_blink = (++idat.blink_count > idat.count >> 1);
+ if (did_blink) {
+ idat.game_blink = !idat.game_blink;
+ idat.blink_count = 0;
+ }
+
+ if (idat.count && did_blink) {
+ const int8_t newx = idat.pos.x + idat.dir;
+ if (!WITHIN(newx, idat.leftmost, idat.rightmost)) { // Invaders reached the edge?
+ idat.dir *= -1; // Invaders change direction
+ idat.pos.y += (INVADER_ROW_H) / 2; // Invaders move down
+ idat.pos.x -= idat.dir; // ...and only move down this time.
+ if (idat.pos.y + idat.botmost * (INVADER_ROW_H) - 2 >= CANNON_Y) // Invaders reached the bottom?
+ kill_cannon(game_state, 20); // Kill the cannon. Reset invaders.
+ }
+
+ idat.pos.x += idat.dir; // Invaders take one step left/right
+
+ // Randomly shoot if invaders are listed
+ if (idat.count && !random(0, 20)) {
+
+ // Find a free bullet
+ laser_t *b = nullptr;
+ LOOP_L_N(i, COUNT(idat.bullet)) if (!idat.bullet[i].v) { b = &idat.bullet[i]; break; }
+ if (b) {
+ // Pick a random shooter and update the bullet
+ //SERIAL_ECHOLNPGM("free bullet found");
+ const uint8_t inv = idat.shooters[random(0, idat.count + 1)], col = inv & 0x0F, row = inv >> 4, type = inv_type[row];
+ b->x = INV_X_CTR(col, type);
+ b->y = INV_Y_BOT(row);
+ b->v = 2 + random(0, 2);
+ }
+ }
+ }
+
+ // Update the laser position
+ if (idat.laser.v) {
+ idat.laser.y += idat.laser.v;
+ if (idat.laser.y < 0) idat.laser.v = 0;
+ }
+
+ // Did the laser collide with an invader?
+ if (idat.laser.v && WITHIN(idat.laser.y, idat.pos.y, idat.pos.y + INVADERS_HIGH - 1)) {
+ const int8_t col = idat.laser_col();
+ if (WITHIN(col, 0, INVADER_COLS - 1)) {
+ const int8_t row = idat.laser_row();
+ if (WITHIN(row, 0, INVADER_ROWS - 1)) {
+ const uint8_t mask = _BV(col);
+ if (idat.bugs[row] & mask) {
+ const uint8_t type = inv_type[row];
+ const int8_t invx = INV_X_LEFT(col, type);
+ if (WITHIN(idat.laser.x, invx, invx + inv_wide[type] - 1)) {
+ // Turn off laser
+ idat.laser.v = 0;
+ // Remove the invader!
+ idat.bugs[row] &= ~mask;
+ // Score!
+ score += INVADER_ROWS - row;
+ // Explode sound!
+ _BUZZ(40, 10);
+ // Explosion bitmap!
+ explode(invx + inv_wide[type] / 2, idat.pos.y + row * (INVADER_ROW_H));
+ // If invaders are gone, go to reset invaders state
+ if (--idat.count) update_invader_data(); else { game_state = 20; reset_bullets(); }
+ } // laser x hit
+ } // invader exists
+ } // good row
+ } // good col
+ } // laser in invader zone
+
+ // Handle alien bullets
+ LOOP_L_N(s, COUNT(idat.bullet)) {
+ laser_t *b = &idat.bullet[s];
+ if (b->v) {
+ // Update alien bullet position
+ b->y += b->v;
+ if (b->y >= LCD_PIXEL_HEIGHT)
+ b->v = 0; // Offscreen
+ else if (b->y >= CANNON_Y && WITHIN(b->x, idat.cannon_x, idat.cannon_x + CANNON_W - 1))
+ kill_cannon(game_state, 120); // Hit the cannon
+ }
+ }
+
+ // Randomly spawn a UFO
+ if (!idat.ufov && !random(0,500)) spawn_ufo();
+
+ // Did the laser hit a ufo?
+ if (idat.laser.v && idat.ufov && idat.laser.y < UFO_H + 2 && WITHIN(idat.laser.x, idat.ufox, idat.ufox + UFO_W - 1)) {
+ // Turn off laser and UFO
+ idat.laser.v = idat.ufov = 0;
+ // Score!
+ score += 10;
+ // Explode!
+ _BUZZ(40, 10);
+ // Explosion bitmap
+ explode(idat.ufox + (UFO_W) / 2, 1);
+ }
+
+ } while (false);
+
+ }
+
+ // Click-and-hold to abort
+ if (ui.button_pressed()) --idat.quit_count; else idat.quit_count = 10;
+
+ // Click to fire or exit
+ if (ui.use_click()) {
+ if (!game_state)
+ idat.quit_count = 0;
+ else if (game_state == 1 && !idat.laser.v)
+ fire_cannon();
+ }
+
+ if (!idat.quit_count) exit_game();
+
+ u8g.setColorIndex(1);
+
+ // Draw invaders
+ if (PAGE_CONTAINS(idat.pos.y, idat.pos.y + idat.botmost * (INVADER_ROW_H) - 2 - 1)) {
+ int8_t yy = idat.pos.y;
+ LOOP_L_N(y, INVADER_ROWS) {
+ const uint8_t type = inv_type[y];
+ if (PAGE_CONTAINS(yy, yy + INVADER_H - 1)) {
+ int8_t xx = idat.pos.x;
+ LOOP_L_N(x, INVADER_COLS) {
+ if (TEST(idat.bugs[y], x))
+ u8g.drawBitmapP(xx, yy, 2, INVADER_H, invader[type][idat.game_blink]);
+ xx += INVADER_COL_W;
+ }
+ }
+ yy += INVADER_ROW_H;
+ }
+ }
+
+ // Draw UFO
+ if (idat.ufov && PAGE_UNDER(UFO_H + 2))
+ u8g.drawBitmapP(idat.ufox, 2, 2, UFO_H, ufo);
+
+ // Draw cannon
+ if (game_state && PAGE_CONTAINS(CANNON_Y, CANNON_Y + CANNON_H - 1) && (game_state < 2 || (game_state & 0x02)))
+ u8g.drawBitmapP(idat.cannon_x, CANNON_Y, 2, CANNON_H, cannon);
+
+ // Draw laser
+ if (idat.laser.v && PAGE_CONTAINS(idat.laser.y, idat.laser.y + LASER_H - 1))
+ u8g.drawVLine(idat.laser.x, idat.laser.y, LASER_H);
+
+ // Draw invader bullets
+ LOOP_L_N (i, COUNT(idat.bullet)) {
+ if (idat.bullet[i].v && PAGE_CONTAINS(idat.bullet[i].y - (SHOT_H - 1), idat.bullet[i].y))
+ u8g.drawVLine(idat.bullet[i].x, idat.bullet[i].y - (SHOT_H - 1), SHOT_H);
+ }
+
+ // Draw explosion
+ if (idat.explod.v && PAGE_CONTAINS(idat.explod.y, idat.explod.y + 7 - 1)) {
+ u8g.drawBitmapP(idat.explod.x, idat.explod.y, 2, 7, explosion);
+ --idat.explod.v;
+ }
+
+ // Blink GAME OVER when game is over
+ if (!game_state) draw_game_over();
+
+ if (PAGE_UNDER(MENU_FONT_ASCENT - 1)) {
+ // Draw Score
+ //const uint8_t sx = (LCD_PIXEL_WIDTH - (score >= 10 ? score >= 100 ? score >= 1000 ? 4 : 3 : 2 : 1) * MENU_FONT_WIDTH) / 2;
+ constexpr uint8_t sx = 0;
+ lcd_put_int(sx, MENU_FONT_ASCENT - 1, score);
+
+ // Draw lives
+ if (idat.cannons_left)
+ for (uint8_t i = 1; i <= idat.cannons_left; ++i)
+ u8g.drawBitmapP(LCD_PIXEL_WIDTH - i * (LIFE_W), 6 - (LIFE_H), 1, LIFE_H, life);
+ }
+
+}
+
+void InvadersGame::enter_game() {
+ init_game(20, game_screen); // countdown to reset invaders
+ idat.cannons_left = 3;
+ idat.quit_count = 10;
+ idat.laser.v = 0;
+ reset_invaders();
+ reset_player();
+}
+
+#endif // MARLIN_INVADERS
diff --git a/Marlin/src/lcd/menu/game/invaders.h b/Marlin/src/lcd/menu/game/invaders.h
new file mode 100644
index 0000000..c99e6c1
--- /dev/null
+++ b/Marlin/src/lcd/menu/game/invaders.h
@@ -0,0 +1,62 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "types.h"
+
+#define INVASION_SIZE 3
+
+#if INVASION_SIZE == 3
+ #define INVADER_COLS 5
+#elif INVASION_SIZE == 4
+ #define INVADER_COLS 6
+#else
+ #define INVADER_COLS 8
+ #undef INVASION_SIZE
+ #define INVASION_SIZE 5
+#endif
+
+#define INVADER_ROWS INVASION_SIZE
+
+#define INVADER_COL_W 14
+#define INVADER_H 8
+#define INVADER_ROW_H (INVADER_H + 2)
+
+typedef struct { int8_t x, y, v; } laser_t;
+
+typedef struct {
+ pos_t pos;
+ uint8_t cannons_left;
+ int8_t cannon_x;
+ laser_t bullet[10], laser, explod;
+ int8_t dir, leftmost, rightmost, botmost;
+ uint8_t count, quit_count, blink_count;
+ uint8_t bugs[INVADER_ROWS], shooters[(INVADER_ROWS) * (INVADER_COLS)];
+ int8_t ufox, ufov;
+ bool game_blink;
+ int8_t laser_col() { return ((laser.x - pos.x) / (INVADER_COL_W)); };
+ int8_t laser_row() { return ((laser.y - pos.y + 2) / (INVADER_ROW_H)); };
+} invaders_data_t;
+
+class InvadersGame : MarlinGame { public: static void enter_game(), game_screen(); };
+
+extern InvadersGame invaders;
diff --git a/Marlin/src/lcd/menu/game/maze.cpp b/Marlin/src/lcd/menu/game/maze.cpp
new file mode 100644
index 0000000..85f752e
--- /dev/null
+++ b/Marlin/src/lcd/menu/game/maze.cpp
@@ -0,0 +1,134 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(MARLIN_MAZE)
+
+#include "game.h"
+
+int8_t move_dir, last_move_dir, // NESW0
+ prizex, prizey, prize_cnt, old_encoder;
+fixed_t playerx, playery;
+
+// Up to 50 lines, then you win!
+typedef struct { int8_t x, y; } pos_t;
+uint8_t head_ind;
+pos_t maze_walls[50] = {
+ { 0, 0 }
+};
+
+// Turn the player cw or ccw
+inline void turn_player(const bool cw) {
+ if (move_dir == 4) move_dir = last_move_dir;
+ move_dir += cw ? 1 : -1;
+ move_dir &= 0x03;
+ last_move_dir = move_dir;
+}
+
+// Reset the player for a new game
+void player_reset() {
+ // Init position
+ playerx = BTOF(1);
+ playery = BTOF(GAME_H / 2);
+
+ // Init motion with a ccw turn
+ move_dir = 0;
+ turn_player(false);
+
+ // Clear prize flag
+ prize_cnt = 255;
+
+ // Clear the controls
+ ui.encoderPosition = 0;
+ old_encoder = 0;
+}
+
+void MazeGame::game_screen() {
+ // Run the sprite logic
+ if (game_frame()) do { // Run logic twice for finer resolution
+
+ // Move the man one unit in the current direction
+ // Direction index 4 is for the stopped man
+ const int8_t oldx = FTOB(playerx), oldy = FTOB(playery);
+ pos_t dir_add[] = { { 0, -1 }, { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, 0 } };
+ playerx += dir_add[move_dir].x;
+ playery += dir_add[move_dir].y;
+ const int8_t x = FTOB(playerx), y = FTOB(playery);
+
+ } while(0);
+
+ u8g.setColorIndex(1);
+
+ // Draw Score
+ if (PAGE_UNDER(HEADER_H)) lcd_put_int(0, HEADER_H - 1, score);
+
+ // Draw the maze
+ // LOOP_L_N(n, head_ind) {
+ // const pos_t &p = maze_walls[n], &q = maze_walls[n + 1];
+ // if (p.x == q.x) {
+ // const int8_t y1 = GAMEY(_MIN(p.y, q.y)), y2 = GAMEY(_MAX(p.y, q.y));
+ // if (PAGE_CONTAINS(y1, y2))
+ // u8g.drawVLine(GAMEX(p.x), y1, y2 - y1 + 1);
+ // }
+ // else if (PAGE_CONTAINS(GAMEY(p.y), GAMEY(p.y))) {
+ // const int8_t x1 = GAMEX(_MIN(p.x, q.x)), x2 = GAMEX(_MAX(p.x, q.x));
+ // u8g.drawHLine(x1, GAMEY(p.y), x2 - x1 + 1);
+ // }
+ // }
+
+ // Draw Man
+ // const int8_t fy = GAMEY(foody);
+ // if (PAGE_CONTAINS(fy, fy + FOOD_WH - 1)) {
+ // const int8_t fx = GAMEX(foodx);
+ // u8g.drawFrame(fx, fy, FOOD_WH, FOOD_WH);
+ // if (FOOD_WH == 5) u8g.drawPixel(fx + 2, fy + 2);
+ // }
+
+ // Draw Ghosts
+ // const int8_t fy = GAMEY(foody);
+ // if (PAGE_CONTAINS(fy, fy + FOOD_WH - 1)) {
+ // const int8_t fx = GAMEX(foodx);
+ // u8g.drawFrame(fx, fy, FOOD_WH, FOOD_WH);
+ // if (FOOD_WH == 5) u8g.drawPixel(fx + 2, fy + 2);
+ // }
+
+ // Draw Prize
+ // if (PAGE_CONTAINS(prizey, prizey + PRIZE_WH - 1)) {
+ // u8g.drawFrame(prizex, prizey, PRIZE_WH, PRIZE_WH);
+ // if (PRIZE_WH == 5) u8g.drawPixel(prizex + 2, prizey + 2);
+ // }
+
+ // Draw GAME OVER
+ if (!game_state) draw_game_over();
+
+ // A click always exits this game
+ if (ui.use_click()) exit_game();
+}
+
+void MazeGame::enter_game() {
+ init_game(1, game_screen); // Game running
+ reset_player();
+ reset_enemies();
+}
+
+#endif // MARLIN_MAZE
diff --git a/Marlin/src/lcd/menu/game/maze.h b/Marlin/src/lcd/menu/game/maze.h
new file mode 100644
index 0000000..ff5dde0
--- /dev/null
+++ b/Marlin/src/lcd/menu/game/maze.h
@@ -0,0 +1,30 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "types.h"
+
+typedef struct { pos_t pos; } maze_data_t;
+
+class MazeGame : MarlinGame { public: static void enter_game(), game_screen(); };
+
+extern MazeGame maze;
diff --git a/Marlin/src/lcd/menu/game/snake.cpp b/Marlin/src/lcd/menu/game/snake.cpp
new file mode 100644
index 0000000..f8892a4
--- /dev/null
+++ b/Marlin/src/lcd/menu/game/snake.cpp
@@ -0,0 +1,323 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(MARLIN_SNAKE)
+
+#include "game.h"
+
+#define SNAKE_BOX 4
+
+#define HEADER_H (MENU_FONT_ASCENT - 2)
+#define SNAKE_WH (SNAKE_BOX + 1)
+
+#define IDEAL_L 2
+#define IDEAL_R (LCD_PIXEL_WIDTH - 1 - 2)
+#define IDEAL_T (HEADER_H + 2)
+#define IDEAL_B (LCD_PIXEL_HEIGHT - 1 - 2)
+#define IDEAL_W (IDEAL_R - (IDEAL_L) + 1)
+#define IDEAL_H (IDEAL_B - (IDEAL_T) + 1)
+
+#define GAME_W int((IDEAL_W) / (SNAKE_WH))
+#define GAME_H int((IDEAL_H) / (SNAKE_WH))
+
+#define BOARD_W ((SNAKE_WH) * (GAME_W) + 1)
+#define BOARD_H ((SNAKE_WH) * (GAME_H) + 1)
+#define BOARD_L ((LCD_PIXEL_WIDTH - (BOARD_W) + 1) / 2)
+#define BOARD_R (BOARD_L + BOARD_W - 1)
+#define BOARD_T (((LCD_PIXEL_HEIGHT + IDEAL_T) - (BOARD_H)) / 2)
+#define BOARD_B (BOARD_T + BOARD_H - 1)
+
+#define GAMEX(X) (BOARD_L + ((X) * (SNAKE_WH)))
+#define GAMEY(Y) (BOARD_T + ((Y) * (SNAKE_WH)))
+
+#if SNAKE_BOX > 2
+ #define FOOD_WH SNAKE_BOX
+#else
+ #define FOOD_WH 2
+#endif
+
+#if SNAKE_BOX < 1
+ #define SNAKE_SIZ 1
+#else
+ #define SNAKE_SIZ SNAKE_BOX
+#endif
+
+constexpr fixed_t snakev = FTOP(0.20);
+
+snake_data_t &sdat = marlin_game_data.snake;
+
+// Remove the first pixel from the tail.
+// If needed, shift out the first segment.
+void shorten_tail() {
+ pos_t &p = sdat.snake_tail[0], &q = sdat.snake_tail[1];
+ bool shift = false;
+ if (p.x == q.x) {
+ // Vertical line
+ p.y += (q.y > p.y) ? 1 : -1;
+ shift = p.y == q.y;
+ }
+ else {
+ // Horizontal line
+ p.x += (q.x > p.x) ? 1 : -1;
+ shift = p.x == q.x;
+ }
+ if (shift) {
+ sdat.head_ind--;
+ LOOP_LE_N(i, sdat.head_ind)
+ sdat.snake_tail[i] = sdat.snake_tail[i + 1];
+ }
+}
+
+// The food is on a line
+inline bool food_on_line() {
+ LOOP_L_N(n, sdat.head_ind) {
+ pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
+ if (p.x == q.x) {
+ if ((sdat.foodx == p.x - 1 || sdat.foodx == p.x) && WITHIN(sdat.foody, _MIN(p.y, q.y), _MAX(p.y, q.y)))
+ return true;
+ }
+ else if ((sdat.foody == p.y - 1 || sdat.foody == p.y) && WITHIN(sdat.foodx, _MIN(p.x, q.x), _MAX(p.x, q.x)))
+ return true;
+ }
+ return false;
+}
+
+// Add a new food blob
+void food_reset() {
+ do {
+ sdat.foodx = random(0, GAME_W);
+ sdat.foody = random(0, GAME_H);
+ } while (food_on_line());
+}
+
+// Turn the snake cw or ccw
+inline void turn_snake(const bool cw) {
+ sdat.snake_dir += cw ? 1 : -1;
+ sdat.snake_dir &= 0x03;
+ sdat.head_ind++;
+ sdat.snake_tail[sdat.head_ind].x = FTOB(sdat.snakex);
+ sdat.snake_tail[sdat.head_ind].y = FTOB(sdat.snakey);
+}
+
+// Reset the snake for a new game
+void snake_reset() {
+ // Init the head and velocity
+ sdat.snakex = BTOF(1);
+ sdat.snakey = BTOF(GAME_H / 2);
+ //snakev = FTOP(0.25);
+
+ // Init the tail with a cw turn
+ sdat.snake_dir = 0;
+ sdat.head_ind = 0;
+ sdat.snake_tail[0].x = 0;
+ sdat.snake_tail[0].y = GAME_H / 2;
+ turn_snake(true);
+
+ // Clear food flag
+ sdat.food_cnt = 5;
+
+ // Clear the controls
+ ui.encoderPosition = 0;
+ sdat.old_encoder = 0;
+}
+
+// Check if head segment overlaps another
+bool snake_overlap() {
+ // 4 lines must exist before a collision is possible
+ if (sdat.head_ind < 4) return false;
+ // Is the last segment crossing any others?
+ const pos_t &h1 = sdat.snake_tail[sdat.head_ind - 1], &h2 = sdat.snake_tail[sdat.head_ind];
+ // VERTICAL head segment?
+ if (h1.x == h2.x) {
+ // Loop from oldest to segment two away from head
+ LOOP_L_N(n, sdat.head_ind - 2) {
+ // Segment p to q
+ const pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
+ if (p.x != q.x) {
+ // Crossing horizontal segment
+ if (WITHIN(h1.x, _MIN(p.x, q.x), _MAX(p.x, q.x)) && (h1.y <= p.y) == (h2.y >= p.y)) return true;
+ } // Overlapping vertical segment
+ else if (h1.x == p.x && _MIN(h1.y, h2.y) <= _MAX(p.y, q.y) && _MAX(h1.y, h2.y) >= _MIN(p.y, q.y)) return true;
+ }
+ }
+ else {
+ // Loop from oldest to segment two away from head
+ LOOP_L_N(n, sdat.head_ind - 2) {
+ // Segment p to q
+ const pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
+ if (p.y != q.y) {
+ // Crossing vertical segment
+ if (WITHIN(h1.y, _MIN(p.y, q.y), _MAX(p.y, q.y)) && (h1.x <= p.x) == (h2.x >= p.x)) return true;
+ } // Overlapping horizontal segment
+ else if (h1.y == p.y && _MIN(h1.x, h2.x) <= _MAX(p.x, q.x) && _MAX(h1.x, h2.x) >= _MIN(p.x, q.x)) return true;
+ }
+ }
+ return false;
+}
+
+void SnakeGame::game_screen() {
+ // Run the snake logic
+ if (game_frame()) do { // Run logic twice for finer resolution
+
+ // Move the snake's head one unit in the current direction
+ const int8_t oldx = FTOB(sdat.snakex), oldy = FTOB(sdat.snakey);
+ switch (sdat.snake_dir) {
+ case 0: sdat.snakey -= snakev; break;
+ case 1: sdat.snakex += snakev; break;
+ case 2: sdat.snakey += snakev; break;
+ case 3: sdat.snakex -= snakev; break;
+ }
+ const int8_t x = FTOB(sdat.snakex), y = FTOB(sdat.snakey);
+
+ // If movement took place...
+ if (oldx != x || oldy != y) {
+
+ if (!WITHIN(x, 0, GAME_W - 1) || !WITHIN(y, 0, GAME_H - 1)) {
+ game_state = 0; // Game Over
+ _BUZZ(400, 40); // Bzzzt!
+ break; // ...out of do-while
+ }
+
+ sdat.snake_tail[sdat.head_ind].x = x;
+ sdat.snake_tail[sdat.head_ind].y = y;
+
+ // Change snake direction if set
+ const int8_t enc = int8_t(ui.encoderPosition), diff = enc - sdat.old_encoder;
+ if (diff) {
+ sdat.old_encoder = enc;
+ turn_snake(diff > 0);
+ }
+
+ if (sdat.food_cnt) --sdat.food_cnt; else shorten_tail();
+
+ // Did the snake collide with itself or go out of bounds?
+ if (snake_overlap()) {
+ game_state = 0; // Game Over
+ _BUZZ(400, 40); // Bzzzt!
+ }
+ // Is the snake at the food?
+ else if (x == sdat.foodx && y == sdat.foody) {
+ _BUZZ(5, 220);
+ _BUZZ(5, 280);
+ score++;
+ sdat.food_cnt = 2;
+ food_reset();
+ }
+ }
+
+ } while(0);
+
+ u8g.setColorIndex(1);
+
+ // Draw Score
+ if (PAGE_UNDER(HEADER_H)) lcd_put_int(0, HEADER_H - 1, score);
+
+ // DRAW THE PLAYFIELD BORDER
+ u8g.drawFrame(BOARD_L - 2, BOARD_T - 2, BOARD_R - BOARD_L + 4, BOARD_B - BOARD_T + 4);
+
+ // Draw the snake (tail)
+ #if SNAKE_WH < 2
+
+ // At this scale just draw a line
+ LOOP_L_N(n, sdat.head_ind) {
+ const pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
+ if (p.x == q.x) {
+ const int8_t y1 = GAMEY(_MIN(p.y, q.y)), y2 = GAMEY(_MAX(p.y, q.y));
+ if (PAGE_CONTAINS(y1, y2))
+ u8g.drawVLine(GAMEX(p.x), y1, y2 - y1 + 1);
+ }
+ else if (PAGE_CONTAINS(GAMEY(p.y), GAMEY(p.y))) {
+ const int8_t x1 = GAMEX(_MIN(p.x, q.x)), x2 = GAMEX(_MAX(p.x, q.x));
+ u8g.drawHLine(x1, GAMEY(p.y), x2 - x1 + 1);
+ }
+ }
+
+ #elif SNAKE_WH == 2
+
+ // At this scale draw two lines
+ LOOP_L_N(n, sdat.head_ind) {
+ const pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
+ if (p.x == q.x) {
+ const int8_t y1 = GAMEY(_MIN(p.y, q.y)), y2 = GAMEY(_MAX(p.y, q.y));
+ if (PAGE_CONTAINS(y1, y2 + 1))
+ u8g.drawFrame(GAMEX(p.x), y1, 2, y2 - y1 + 1 + 1);
+ }
+ else {
+ const int8_t py = GAMEY(p.y);
+ if (PAGE_CONTAINS(py, py + 1)) {
+ const int8_t x1 = GAMEX(_MIN(p.x, q.x)), x2 = GAMEX(_MAX(p.x, q.x));
+ u8g.drawFrame(x1, py, x2 - x1 + 1 + 1, 2);
+ }
+ }
+ }
+
+ #else
+
+ // Draw a series of boxes
+ LOOP_L_N(n, sdat.head_ind) {
+ const pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
+ if (p.x == q.x) {
+ const int8_t y1 = _MIN(p.y, q.y), y2 = _MAX(p.y, q.y);
+ if (PAGE_CONTAINS(GAMEY(y1), GAMEY(y2) + SNAKE_SIZ - 1)) {
+ for (int8_t i = y1; i <= y2; ++i) {
+ const int8_t y = GAMEY(i);
+ if (PAGE_CONTAINS(y, y + SNAKE_SIZ - 1))
+ u8g.drawBox(GAMEX(p.x), y, SNAKE_SIZ, SNAKE_SIZ);
+ }
+ }
+ }
+ else {
+ const int8_t py = GAMEY(p.y);
+ if (PAGE_CONTAINS(py, py + SNAKE_SIZ - 1)) {
+ const int8_t x1 = _MIN(p.x, q.x), x2 = _MAX(p.x, q.x);
+ for (int8_t i = x1; i <= x2; ++i)
+ u8g.drawBox(GAMEX(i), py, SNAKE_SIZ, SNAKE_SIZ);
+ }
+ }
+ }
+
+ #endif
+
+ // Draw food
+ const int8_t fy = GAMEY(sdat.foody);
+ if (PAGE_CONTAINS(fy, fy + FOOD_WH - 1)) {
+ const int8_t fx = GAMEX(sdat.foodx);
+ u8g.drawFrame(fx, fy, FOOD_WH, FOOD_WH);
+ if (FOOD_WH == 5) u8g.drawPixel(fx + 2, fy + 2);
+ }
+
+ // Draw GAME OVER
+ if (!game_state) draw_game_over();
+
+ // A click always exits this game
+ if (ui.use_click()) exit_game();
+}
+
+void SnakeGame::enter_game() {
+ init_game(1, game_screen); // 1 = Game running
+ snake_reset();
+ food_reset();
+}
+
+#endif // MARLIN_SNAKE
diff --git a/Marlin/src/lcd/menu/game/snake.h b/Marlin/src/lcd/menu/game/snake.h
new file mode 100644
index 0000000..09a3a72
--- /dev/null
+++ b/Marlin/src/lcd/menu/game/snake.h
@@ -0,0 +1,38 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "types.h"
+
+typedef struct {
+ int8_t snake_dir, // NESW
+ foodx, foody,
+ food_cnt,
+ old_encoder;
+ pos_t snake_tail[50];
+ fixed_t snakex, snakey;
+ uint8_t head_ind;
+} snake_data_t;
+
+class SnakeGame : MarlinGame { public: static void enter_game(), game_screen(); };
+
+extern SnakeGame snake;
diff --git a/Marlin/src/lcd/menu/game/types.h b/Marlin/src/lcd/menu/game/types.h
new file mode 100644
index 0000000..f6e6c78
--- /dev/null
+++ b/Marlin/src/lcd/menu/game/types.h
@@ -0,0 +1,46 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include <stdint.h>
+
+typedef struct { int8_t x, y; } pos_t;
+
+// Simple 8:8 fixed-point
+typedef int16_t fixed_t;
+#define FTOP(F) fixed_t((F)*256.0f)
+#define PTOF(P) (float(P)*(1.0f/256.0f))
+#define BTOF(X) (fixed_t(X)<<8)
+#define FTOB(X) int8_t(fixed_t(X)>>8)
+
+class MarlinGame {
+protected:
+ static int score;
+ static uint8_t game_state;
+ static millis_t next_frame;
+
+ static bool game_frame();
+ static void draw_game_over();
+ static void exit_game();
+public:
+ static void init_game(const uint8_t init_state, const screenFunc_t screen);
+};
diff --git a/Marlin/src/lcd/menu/menu.cpp b/Marlin/src/lcd/menu/menu.cpp
new file mode 100644
index 0000000..add306b
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu.cpp
@@ -0,0 +1,385 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_LCD_MENU
+
+#include "menu.h"
+#include "../../module/planner.h"
+#include "../../module/motion.h"
+#include "../../module/printcounter.h"
+#include "../../gcode/queue.h"
+
+#if HAS_BUZZER
+ #include "../../libs/buzzer.h"
+#endif
+
+#if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ #include "../../module/probe.h"
+#endif
+
+#if EITHER(ENABLE_LEVELING_FADE_HEIGHT, AUTO_BED_LEVELING_UBL)
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+////////////////////////////////////////////
+///////////// Global Variables /////////////
+////////////////////////////////////////////
+
+// Menu Navigation
+int8_t encoderTopLine, encoderLine, screen_items;
+
+typedef struct {
+ screenFunc_t menu_function;
+ uint32_t encoder_position;
+ int8_t top_line, items;
+} menuPosition;
+menuPosition screen_history[6];
+uint8_t screen_history_depth = 0;
+
+int8_t MenuItemBase::itemIndex; // Index number for draw and action
+PGM_P MenuItemBase::itemString; // A PSTR for substitution
+chimera_t editable; // Value Editing
+
+// Menu Edit Items
+PGM_P MenuEditItemBase::editLabel;
+void* MenuEditItemBase::editValue;
+int32_t MenuEditItemBase::minEditValue,
+ MenuEditItemBase::maxEditValue;
+screenFunc_t MenuEditItemBase::callbackFunc;
+bool MenuEditItemBase::liveEdit;
+
+////////////////////////////////////////////
+//////// Menu Navigation & History /////////
+////////////////////////////////////////////
+
+void MarlinUI::return_to_status() { goto_screen(status_screen); }
+
+void MarlinUI::save_previous_screen() {
+ if (screen_history_depth < COUNT(screen_history))
+ screen_history[screen_history_depth++] = { currentScreen, encoderPosition, encoderTopLine, screen_items };
+}
+
+void MarlinUI::_goto_previous_screen(TERN_(TURBO_BACK_MENU_ITEM, const bool is_back/*=false*/)) {
+ IF_DISABLED(TURBO_BACK_MENU_ITEM, constexpr bool is_back = false);
+ TERN_(HAS_TOUCH_BUTTONS, on_edit_screen = false);
+ if (screen_history_depth > 0) {
+ menuPosition &sh = screen_history[--screen_history_depth];
+ goto_screen(sh.menu_function,
+ is_back ? 0 : sh.encoder_position,
+ is_back ? 0 : sh.top_line,
+ sh.items
+ );
+ }
+ else
+ return_to_status();
+}
+
+////////////////////////////////////////////
+/////////// Menu Editing Actions ///////////
+////////////////////////////////////////////
+
+/**
+ * Functions for editing single values
+ *
+ * The "DEFINE_MENU_EDIT_ITEM" macro generates the classes needed to edit a numerical value.
+ *
+ * The prerequisite is that in the header the type was already declared:
+ *
+ * DEFINE_MENU_EDIT_ITEM_TYPE(int3, int16_t, i16tostr3rj, 1)
+ *
+ * For example, DEFINE_MENU_EDIT_ITEM(int3) expands into:
+ *
+ * template class TMenuEditItem<MenuEditItemInfo_int3>
+ *
+ * You can then use one of the menu macros to present the edit interface:
+ * EDIT_ITEM(int3, MSG_SPEED, &feedrate_percentage, 10, 999)
+ *
+ * This expands into a more primitive menu item:
+ * _MENU_ITEM_P(int3, false, GET_TEXT(MSG_SPEED), &feedrate_percentage, 10, 999)
+ *
+ * ...which calls:
+ * MenuItem_int3::action(plabel, &feedrate_percentage, 10, 999)
+ * MenuItem_int3::draw(encoderLine == _thisItemNr, _lcdLineNr, plabel, &feedrate_percentage, 10, 999)
+ */
+void MenuEditItemBase::edit_screen(strfunc_t strfunc, loadfunc_t loadfunc) {
+ TERN_(HAS_TOUCH_BUTTONS, ui.repeat_delay = BUTTON_DELAY_EDIT);
+ 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(strfunc(ui.encoderPosition + minEditValue));
+ if (ui.lcd_clicked || (liveEdit && ui.should_draw())) {
+ if (editValue) loadfunc(editValue, ui.encoderPosition + minEditValue);
+ if (callbackFunc && (liveEdit || ui.lcd_clicked)) (*callbackFunc)();
+ if (ui.use_click()) ui.goto_previous_screen();
+ }
+}
+
+void MenuEditItemBase::goto_edit_screen(
+ PGM_P const el, // Edit label
+ void * const ev, // Edit value pointer
+ const int32_t minv, // Encoder minimum
+ const int32_t maxv, // Encoder maximum
+ const uint16_t ep, // Initial encoder value
+ const screenFunc_t cs, // MenuItem_type::draw_edit_screen => MenuEditItemBase::edit()
+ const screenFunc_t cb, // Callback after edit
+ const bool le // Flag to call cb() during editing
+) {
+ TERN_(HAS_TOUCH_BUTTONS, ui.on_edit_screen = true);
+ ui.screen_changed = true;
+ ui.save_previous_screen();
+ ui.refresh();
+ editLabel = el;
+ editValue = ev;
+ minEditValue = minv;
+ maxEditValue = maxv;
+ ui.encoderPosition = ep;
+ ui.currentScreen = cs;
+ callbackFunc = cb;
+ liveEdit = le;
+}
+
+////////////////////////////////////////////
+///////////////// Menu Tree ////////////////
+////////////////////////////////////////////
+
+#include "../../MarlinCore.h"
+
+bool printer_busy() {
+ return planner.movesplanned() || printingIsActive();
+}
+
+/**
+ * General function to go directly to a screen
+ */
+void MarlinUI::goto_screen(screenFunc_t screen, const uint16_t encoder/*=0*/, const uint8_t top/*=0*/, const uint8_t items/*=0*/) {
+ if (currentScreen != screen) {
+
+ TERN_(HAS_TOUCH_BUTTONS, repeat_delay = BUTTON_DELAY_MENU);
+
+ TERN_(LCD_SET_PROGRESS_MANUALLY, progress_reset());
+
+ #if BOTH(DOUBLECLICK_FOR_Z_BABYSTEPPING, BABYSTEPPING)
+ static millis_t doubleclick_expire_ms = 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 (on_status_screen())
+ doubleclick_expire_ms = millis() + DOUBLECLICK_MAX_INTERVAL;
+ }
+ else if (screen == status_screen && currentScreen == menu_main && PENDING(millis(), doubleclick_expire_ms)) {
+ if (BABYSTEP_ALLOWED())
+ screen = TERN(BABYSTEP_ZPROBE_OFFSET, lcd_babystep_zoffset, lcd_babystep_z);
+ else {
+ #if ENABLED(MOVE_Z_WHEN_IDLE)
+ ui.manual_move.menu_scale = MOVE_Z_IDLE_MULTIPLICATOR;
+ screen = lcd_move_z;
+ #endif
+ }
+ }
+ #endif
+
+ currentScreen = screen;
+ encoderPosition = encoder;
+ encoderTopLine = top;
+ screen_items = items;
+ if (on_status_screen()) {
+ defer_status_screen(false);
+ clear_menu_history();
+ TERN_(AUTO_BED_LEVELING_UBL, ubl.lcd_map_control = false);
+ }
+
+ clear_lcd();
+
+ // Re-initialize custom characters that may be re-used
+ #if HAS_MARLINUI_HD44780
+ if (TERN1(AUTO_BED_LEVELING_UBL, !ubl.lcd_map_control))
+ set_custom_characters(on_status_screen() ? CHARSET_INFO : CHARSET_MENU);
+ #endif
+
+ refresh(LCDVIEW_CALL_REDRAW_NEXT);
+ screen_changed = true;
+ TERN_(HAS_MARLINUI_U8GLIB, drawing_screen = false);
+
+ TERN_(HAS_LCD_MENU, encoder_direction_normal());
+
+ set_selection(false);
+ }
+}
+
+////////////////////////////////////////////
+///////////// Manual Movement //////////////
+////////////////////////////////////////////
+
+//
+// Display a "synchronize" screen with a custom message until
+// all moves are finished. Go back to calling screen when done.
+//
+void MarlinUI::synchronize(PGM_P const msg/*=nullptr*/) {
+ static PGM_P sync_message = msg ?: GET_TEXT(MSG_MOVING);
+ save_previous_screen();
+ goto_screen([]{
+ if (should_draw()) MenuItem_static::draw(LCD_HEIGHT >= 4, sync_message);
+ });
+ defer_status_screen();
+ planner.synchronize(); // idle() is called until moves complete
+ goto_previous_screen_no_defer();
+}
+
+/**
+ * Scrolling for menus and other line-based screens
+ *
+ * encoderLine is the position based on the encoder
+ * encoderTopLine is the top menu line to display
+ * _lcdLineNr is the index of the LCD line (e.g., 0-3)
+ * _menuLineNr is the menu item to draw and process
+ * _thisItemNr is the index of each MENU_ITEM or STATIC_ITEM
+ * screen_items is the total number of items in the menu (after one call)
+ */
+void scroll_screen(const uint8_t limit, const bool is_menu) {
+ ui.encoder_direction_menus();
+ ENCODER_RATE_MULTIPLY(false);
+ if (int32_t(ui.encoderPosition) < 0) ui.encoderPosition = 0;
+ if (ui.first_page) {
+ encoderLine = ui.encoderPosition / (ENCODER_STEPS_PER_MENU_ITEM);
+ ui.screen_changed = false;
+ }
+ if (screen_items > 0 && encoderLine >= screen_items - limit) {
+ encoderLine = _MAX(0, screen_items - limit);
+ ui.encoderPosition = encoderLine * (ENCODER_STEPS_PER_MENU_ITEM);
+ }
+ if (is_menu) {
+ NOMORE(encoderTopLine, encoderLine);
+ if (encoderLine >= encoderTopLine + LCD_HEIGHT)
+ encoderTopLine = encoderLine - LCD_HEIGHT + 1;
+ }
+ else
+ encoderTopLine = encoderLine;
+}
+
+#if HAS_BUZZER
+ void MarlinUI::completion_feedback(const bool good/*=true*/) {
+ if (good) {
+ BUZZ(100, 659);
+ BUZZ(100, 698);
+ }
+ else BUZZ(20, 440);
+ }
+#endif
+
+#if HAS_LINE_TO_Z
+
+ void line_to_z(const float &z) {
+ current_position.z = z;
+ line_to_current_position(manual_feedrate_mm_s.z);
+ }
+
+#endif
+
+#if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+
+ #include "../../feature/babystep.h"
+
+ void lcd_babystep_zoffset() {
+ if (ui.use_click()) return ui.goto_previous_screen_no_defer();
+ ui.defer_status_screen();
+ const bool do_probe = DISABLED(BABYSTEP_HOTEND_Z_OFFSET) || active_extruder == 0;
+ if (ui.encoderPosition) {
+ const int16_t babystep_increment = int16_t(ui.encoderPosition) * (BABYSTEP_SIZE_Z);
+ ui.encoderPosition = 0;
+
+ const float diff = planner.steps_to_mm[Z_AXIS] * babystep_increment,
+ new_probe_offset = probe.offset.z + diff,
+ new_offs = TERN(BABYSTEP_HOTEND_Z_OFFSET
+ , do_probe ? new_probe_offset : hotend_offset[active_extruder].z - diff
+ , new_probe_offset
+ );
+ if (WITHIN(new_offs, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX)) {
+
+ babystep.add_steps(Z_AXIS, babystep_increment);
+
+ if (do_probe)
+ probe.offset.z = new_offs;
+ else
+ TERN(BABYSTEP_HOTEND_Z_OFFSET, hotend_offset[active_extruder].z = new_offs, NOOP);
+
+ ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
+ }
+ }
+ if (ui.should_draw()) {
+ if (do_probe) {
+ MenuEditItemBase::draw_edit_screen(GET_TEXT(MSG_ZPROBE_ZOFFSET), BABYSTEP_TO_STR(probe.offset.z));
+ TERN_(BABYSTEP_ZPROBE_GFX_OVERLAY, _lcd_zoffset_overlay_gfx(probe.offset.z));
+ }
+ else {
+ #if ENABLED(BABYSTEP_HOTEND_Z_OFFSET)
+ MenuEditItemBase::draw_edit_screen(GET_TEXT(MSG_HOTEND_OFFSET_Z), ftostr54sign(hotend_offset[active_extruder].z));
+ #endif
+ }
+ }
+ }
+
+#endif // BABYSTEP_ZPROBE_OFFSET
+
+void _lcd_draw_homing() {
+ if (ui.should_draw()) {
+ constexpr uint8_t line = (LCD_HEIGHT - 1) / 2;
+ MenuItem_static::draw(line, GET_TEXT(MSG_LEVEL_BED_HOMING));
+ }
+}
+
+#if ENABLED(LCD_BED_LEVELING) || (HAS_LEVELING && DISABLED(SLIM_LCD_MENUS))
+ #include "../../feature/bedlevel/bedlevel.h"
+ void _lcd_toggle_bed_leveling() { set_bed_leveling_enabled(!planner.leveling_active); }
+#endif
+
+//
+// Selection screen presents a prompt and two options
+//
+bool MarlinUI::selection; // = false
+bool MarlinUI::update_selection() {
+ encoder_direction_select();
+ if (encoderPosition) {
+ selection = int16_t(encoderPosition) > 0;
+ encoderPosition = 0;
+ }
+ return selection;
+}
+
+void MenuItem_confirm::select_screen(
+ PGM_P const yes, PGM_P const no,
+ selectFunc_t yesFunc, selectFunc_t noFunc,
+ PGM_P const pref, const char * const string/*=nullptr*/, PGM_P const suff/*=nullptr*/
+) {
+ const bool ui_selection = ui.update_selection(), got_click = ui.use_click();
+ if (got_click || ui.should_draw()) {
+ draw_select_screen(yes, no, ui_selection, pref, string, suff);
+ if (got_click) {
+ selectFunc_t callFunc = ui_selection ? yesFunc : noFunc;
+ if (callFunc) callFunc(); else ui.goto_previous_screen();
+ }
+ ui.defer_status_screen();
+ }
+}
+
+#endif // HAS_LCD_MENU
diff --git a/Marlin/src/lcd/menu/menu.h b/Marlin/src/lcd/menu/menu.h
new file mode 100644
index 0000000..de11ee3
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu.h
@@ -0,0 +1,256 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "../marlinui.h"
+#include "../../libs/numtostr.h"
+#include "../../inc/MarlinConfig.h"
+
+#include "limits.h"
+
+extern int8_t encoderLine, encoderTopLine, screen_items;
+
+void scroll_screen(const uint8_t limit, const bool is_menu);
+bool printer_busy();
+
+typedef void (*selectFunc_t)();
+
+#define SS_LEFT 0x00
+#define SS_CENTER 0x01
+#define SS_INVERT 0x02
+#define SS_DEFAULT SS_CENTER
+
+#if HAS_MARLINUI_U8GLIB && EITHER(BABYSTEP_ZPROBE_GFX_OVERLAY, MESH_EDIT_GFX_OVERLAY)
+ void _lcd_zoffset_overlay_gfx(const float zvalue);
+#endif
+
+#if ENABLED(BABYSTEP_ZPROBE_OFFSET) && Z_PROBE_OFFSET_RANGE_MIN >= -9 && Z_PROBE_OFFSET_RANGE_MAX <= 9
+ #define BABYSTEP_TO_STR(N) ftostr43sign(N)
+#elif ENABLED(BABYSTEPPING)
+ #define BABYSTEP_TO_STR(N) ftostr53sign(N)
+#endif
+
+////////////////////////////////////////////
+///////////// Base Menu Items //////////////
+////////////////////////////////////////////
+
+class MenuItemBase {
+ public:
+ // Index to interject in the item label and/or for use by its action.
+ static int8_t itemIndex;
+
+ // An optional pointer for use in display or by the action
+ static PGM_P itemString;
+
+ // Store the index of the item ahead of use by indexed items
+ FORCE_INLINE static void init(const int8_t ind=0, PGM_P const pstr=nullptr) { itemIndex = ind; itemString = pstr; }
+
+ // Draw an item either selected (pre_char) or not (space) with post_char
+ static void _draw(const bool sel, const uint8_t row, PGM_P const pstr, const char pre_char, const char post_char);
+
+ // Draw an item either selected ('>') or not (space) with post_char
+ FORCE_INLINE static void _draw(const bool sel, const uint8_t row, PGM_P const pstr, const char post_char) {
+ _draw(sel, row, pstr, '>', post_char);
+ }
+};
+
+// STATIC_ITEM(LABEL,...)
+class MenuItem_static : public MenuItemBase {
+ public:
+ static void draw(const uint8_t row, PGM_P const pstr, const uint8_t style=SS_DEFAULT, const char * const vstr=nullptr);
+};
+
+// BACK_ITEM(LABEL)
+class MenuItem_back : public MenuItemBase {
+ public:
+ FORCE_INLINE static void draw(const bool sel, const uint8_t row, PGM_P const pstr) {
+ _draw(sel, row, pstr, LCD_STR_UPLEVEL[0], LCD_STR_UPLEVEL[0]);
+ }
+ // Back Item action goes back one step in history
+ FORCE_INLINE static void action(PGM_P const=nullptr) { ui.go_back(); }
+};
+
+// CONFIRM_ITEM(LABEL,Y,N,FY,FN,...),
+// YESNO_ITEM(LABEL,FY,FN,...)
+class MenuItem_confirm : public MenuItemBase {
+ public:
+ FORCE_INLINE static void draw(const bool sel, const uint8_t row, PGM_P const pstr, ...) {
+ _draw(sel, row, pstr, '>', LCD_STR_ARROW_RIGHT[0]);
+ }
+ // Implemented for HD44780 and DOGM
+ // Draw the prompt, buttons, and state
+ static void draw_select_screen(
+ PGM_P const yes, // Right option label
+ PGM_P const no, // Left option label
+ const bool yesno, // Is "yes" selected?
+ PGM_P const pref, // Prompt prefix
+ const char * const string, // Prompt runtime string
+ PGM_P const suff // Prompt suffix
+ );
+ static void select_screen(
+ PGM_P const yes, PGM_P const no,
+ selectFunc_t yesFunc, selectFunc_t noFunc,
+ PGM_P const pref, const char * const string=nullptr, PGM_P const suff=nullptr
+ );
+ static inline void select_screen(
+ PGM_P const yes, PGM_P const no,
+ selectFunc_t yesFunc, selectFunc_t noFunc,
+ PGM_P const pref, const progmem_str string, PGM_P const suff=nullptr
+ ) {
+ char str[strlen_P((PGM_P)string) + 1];
+ strcpy_P(str, (PGM_P)string);
+ select_screen(yes, no, yesFunc, noFunc, pref, str, suff);
+ }
+ // Shortcut for prompt with "NO"/ "YES" labels
+ FORCE_INLINE static void confirm_screen(selectFunc_t yesFunc, selectFunc_t noFunc, PGM_P const pref, const char * const string=nullptr, PGM_P const suff=nullptr) {
+ select_screen(GET_TEXT(MSG_YES), GET_TEXT(MSG_NO), yesFunc, noFunc, pref, string, suff);
+ }
+};
+
+////////////////////////////////////////////
+///////////// Edit Menu Items //////////////
+////////////////////////////////////////////
+
+// The Menu Edit shadow value
+typedef union {
+ bool state;
+ float decimal;
+ int8_t int8;
+ int16_t int16;
+ int32_t int32;
+ uint8_t uint8;
+ uint16_t uint16;
+ uint32_t uint32;
+} chimera_t;
+extern chimera_t editable;
+
+// Base class for Menu Edit Items
+class MenuEditItemBase : public MenuItemBase {
+ private:
+ // These values are statically constructed by init() via action()
+ // The action() method acts like the instantiator. The entire lifespan
+ // of a menu item is within its declaration, so all these values decompose
+ // into behavior and unused items get optimized out.
+ static PGM_P editLabel;
+ static void *editValue;
+ static int32_t minEditValue, maxEditValue; // Encoder value range
+ static screenFunc_t callbackFunc;
+ static bool liveEdit;
+ protected:
+ typedef const char* (*strfunc_t)(const int32_t);
+ typedef void (*loadfunc_t)(void *, const int32_t);
+ static void goto_edit_screen(
+ PGM_P const el, // Edit label
+ void * const ev, // Edit value pointer
+ const int32_t minv, // Encoder minimum
+ const int32_t maxv, // Encoder maximum
+ const uint16_t ep, // Initial encoder value
+ const screenFunc_t cs, // MenuItem_type::draw_edit_screen => MenuEditItemBase::edit()
+ const screenFunc_t cb, // Callback after edit
+ const bool le // Flag to call cb() during editing
+ );
+ static void edit_screen(strfunc_t, loadfunc_t); // Edit value handler
+ public:
+ // Implemented for HD44780 and DOGM
+ // Draw the current item at specified row with edit data
+ static void draw(const bool sel, const uint8_t row, PGM_P const pstr, const char* const inStr, const bool pgm=false);
+
+ // Implemented for HD44780 and DOGM
+ // This low-level method is good to draw from anywhere
+ static void draw_edit_screen(PGM_P const pstr, const char* const value);
+
+ // This method is for the current menu item
+ static inline void draw_edit_screen(const char* const value) { draw_edit_screen(editLabel, value); }
+};
+
+#if ENABLED(SDSUPPORT)
+ class CardReader;
+ class MenuItem_sdbase {
+ public:
+ // Implemented for HD44780 and DOGM
+ static void draw(const bool sel, const uint8_t row, PGM_P const pstr, CardReader &theCard, const bool isDir);
+ };
+#endif
+
+////////////////////////////////////////////
+/////////////// Menu Screens ///////////////
+////////////////////////////////////////////
+
+void menu_main();
+void menu_move();
+
+#if ENABLED(SDSUPPORT)
+ void menu_media();
+#endif
+
+////////////////////////////////////////////
+//////// Menu Item Helper Functions ////////
+////////////////////////////////////////////
+
+void lcd_move_z();
+void _lcd_draw_homing();
+
+#define HAS_LINE_TO_Z ANY(DELTA, PROBE_MANUALLY, MESH_BED_LEVELING, LEVEL_BED_CORNERS)
+
+#if HAS_LINE_TO_Z
+ void line_to_z(const float &z);
+#endif
+
+#if HAS_MARLINUI_U8GLIB && EITHER(BABYSTEP_ZPROBE_GFX_OVERLAY, MESH_EDIT_GFX_OVERLAY)
+ void _lcd_zoffset_overlay_gfx(const float zvalue);
+#endif
+
+#if ENABLED(PROBE_OFFSET_WIZARD)
+ void goto_probe_offset_wizard();
+#endif
+
+#if ENABLED(LCD_BED_LEVELING) || (HAS_LEVELING && DISABLED(SLIM_LCD_MENUS))
+ void _lcd_toggle_bed_leveling();
+#endif
+
+#if ENABLED(BABYSTEPPING)
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ void lcd_babystep_zoffset();
+ #else
+ void lcd_babystep_z();
+ #endif
+
+ #if ENABLED(BABYSTEP_MILLIMETER_UNITS)
+ #define BABYSTEP_SIZE_X int32_t((BABYSTEP_MULTIPLICATOR_XY) * planner.settings.axis_steps_per_mm[X_AXIS])
+ #define BABYSTEP_SIZE_Y int32_t((BABYSTEP_MULTIPLICATOR_XY) * planner.settings.axis_steps_per_mm[Y_AXIS])
+ #define BABYSTEP_SIZE_Z int32_t((BABYSTEP_MULTIPLICATOR_Z) * planner.settings.axis_steps_per_mm[Z_AXIS])
+ #else
+ #define BABYSTEP_SIZE_X BABYSTEP_MULTIPLICATOR_XY
+ #define BABYSTEP_SIZE_Y BABYSTEP_MULTIPLICATOR_XY
+ #define BABYSTEP_SIZE_Z BABYSTEP_MULTIPLICATOR_Z
+ #endif
+
+#endif
+
+#if ENABLED(TOUCH_SCREEN_CALIBRATION)
+ void touch_screen_calibration();
+#endif
+
+extern uint8_t screen_history_depth;
+inline void clear_menu_history() { screen_history_depth = 0; }
+
+#define STICKY_SCREEN(S) []{ ui.defer_status_screen(); ui.goto_screen(S); }
diff --git a/Marlin/src/lcd/menu/menu_addon.h b/Marlin/src/lcd/menu/menu_addon.h
new file mode 100644
index 0000000..73e7061
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_addon.h
@@ -0,0 +1,33 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "../lcdprint.h"
+
+#define _MENU_ITEM_ADDON_START(N,X) do{ \
+ if (ui.should_draw() && _menuLineNr == _thisItemNr - 1) { \
+ N(X)
+
+#define MENU_ITEM_ADDON_START(X) _MENU_ITEM_ADDON_START(SETCURSOR_X, X)
+#define MENU_ITEM_ADDON_START_RJ(X) _MENU_ITEM_ADDON_START(SETCURSOR_X_RJ, X)
+
+#define MENU_ITEM_ADDON_END() } }while(0)
diff --git a/Marlin/src/lcd/menu/menu_advanced.cpp b/Marlin/src/lcd/menu/menu_advanced.cpp
new file mode 100644
index 0000000..cb78271
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_advanced.cpp
@@ -0,0 +1,630 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Advanced Settings Menus
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_LCD_MENU
+
+#include "menu_item.h"
+#include "../../module/planner.h"
+
+#if DISABLED(NO_VOLUMETRICS)
+ #include "../../gcode/parser.h"
+#endif
+
+#if HAS_BED_PROBE
+ #include "../../module/probe.h"
+#endif
+
+#if ENABLED(PIDTEMP)
+ #include "../../module/temperature.h"
+#endif
+
+#if HAS_FILAMENT_RUNOUT_DISTANCE
+ #include "../../feature/runout.h"
+#endif
+
+#if ENABLED(SD_FIRMWARE_UPDATE)
+ #include "../../module/settings.h"
+#endif
+
+#if ENABLED(PASSWORD_FEATURE)
+ #include "../../feature/password/password.h"
+#endif
+
+void menu_tmc();
+void menu_backlash();
+
+#if ENABLED(HAS_MOTOR_CURRENT_DAC)
+
+ #include "../../feature/dac/stepper_dac.h"
+
+ void menu_dac() {
+ static xyze_uint8_t driverPercent;
+ LOOP_XYZE(i) driverPercent[i] = stepper_dac.get_current_percent((AxisEnum)i);
+ START_MENU();
+ BACK_ITEM(MSG_ADVANCED_SETTINGS);
+ #define EDIT_DAC_PERCENT(A) EDIT_ITEM(uint8, MSG_DAC_PERCENT_##A, &driverPercent[_AXIS(A)], 0, 100, []{ stepper_dac.set_current_percents(driverPercent); })
+ EDIT_DAC_PERCENT(X);
+ EDIT_DAC_PERCENT(Y);
+ EDIT_DAC_PERCENT(Z);
+ EDIT_DAC_PERCENT(E);
+ ACTION_ITEM(MSG_DAC_EEPROM_WRITE, stepper_dac.commit_eeprom);
+ END_MENU();
+ }
+
+#endif
+
+#if HAS_MOTOR_CURRENT_PWM
+
+ #include "../../module/stepper.h"
+
+ void menu_pwm() {
+ START_MENU();
+ BACK_ITEM(MSG_ADVANCED_SETTINGS);
+ #define EDIT_CURRENT_PWM(LABEL,I) EDIT_ITEM_P(long5, PSTR(LABEL), &stepper.motor_current_setting[I], 100, 2000, stepper.refresh_motor_power)
+ #if ANY_PIN(MOTOR_CURRENT_PWM_XY, MOTOR_CURRENT_PWM_X, MOTOR_CURRENT_PWM_Y)
+ EDIT_CURRENT_PWM(STR_X STR_Y, 0);
+ #endif
+ #if PIN_EXISTS(MOTOR_CURRENT_PWM_Z)
+ EDIT_CURRENT_PWM(STR_Z, 1);
+ #endif
+ #if PIN_EXISTS(MOTOR_CURRENT_PWM_E)
+ EDIT_CURRENT_PWM(STR_E, 2);
+ #endif
+ END_MENU();
+ }
+
+#endif
+
+#if DISABLED(NO_VOLUMETRICS) || ENABLED(ADVANCED_PAUSE_FEATURE)
+ //
+ // Advanced Settings > Filament
+ //
+ void menu_advanced_filament() {
+ START_MENU();
+ BACK_ITEM(MSG_ADVANCED_SETTINGS);
+
+ #if ENABLED(LIN_ADVANCE)
+ #if EXTRUDERS == 1
+ EDIT_ITEM(float42_52, MSG_ADVANCE_K, &planner.extruder_advance_K[0], 0, 10);
+ #elif HAS_MULTI_EXTRUDER
+ LOOP_L_N(n, EXTRUDERS)
+ EDIT_ITEM_N(float42_52, n, MSG_ADVANCE_K_E, &planner.extruder_advance_K[n], 0, 10);
+ #endif
+ #endif
+
+ #if DISABLED(NO_VOLUMETRICS)
+ EDIT_ITEM(bool, MSG_VOLUMETRIC_ENABLED, &parser.volumetric_enabled, planner.calculate_volumetric_multipliers);
+
+ #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT)
+ EDIT_ITEM_FAST(float42_52, MSG_VOLUMETRIC_LIMIT, &planner.volumetric_extruder_limit[active_extruder], 0.0f, 20.0f, planner.calculate_volumetric_extruder_limits);
+ #if HAS_MULTI_EXTRUDER
+ LOOP_L_N(n, EXTRUDERS)
+ EDIT_ITEM_FAST_N(float42_52, n, MSG_VOLUMETRIC_LIMIT_E, &planner.volumetric_extruder_limit[n], 0.0f, 20.00f, planner.calculate_volumetric_extruder_limits);
+ #endif
+ #endif
+
+ if (parser.volumetric_enabled) {
+ EDIT_ITEM_FAST(float43, MSG_FILAMENT_DIAM, &planner.filament_size[active_extruder], 1.5f, 3.25f, planner.calculate_volumetric_multipliers);
+ #if HAS_MULTI_EXTRUDER
+ LOOP_L_N(n, EXTRUDERS)
+ EDIT_ITEM_FAST_N(float43, n, MSG_FILAMENT_DIAM_E, &planner.filament_size[n], 1.5f, 3.25f, planner.calculate_volumetric_multipliers);
+ #endif
+ }
+ #endif
+
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ constexpr float extrude_maxlength = TERN(PREVENT_LENGTHY_EXTRUDE, EXTRUDE_MAXLENGTH, 999);
+
+ EDIT_ITEM_FAST(float4, MSG_FILAMENT_UNLOAD, &fc_settings[active_extruder].unload_length, 0, extrude_maxlength);
+ #if HAS_MULTI_EXTRUDER
+ LOOP_L_N(n, EXTRUDERS)
+ EDIT_ITEM_FAST_N(float4, n, MSG_FILAMENTUNLOAD_E, &fc_settings[n].unload_length, 0, extrude_maxlength);
+ #endif
+
+ EDIT_ITEM_FAST(float4, MSG_FILAMENT_LOAD, &fc_settings[active_extruder].load_length, 0, extrude_maxlength);
+ #if HAS_MULTI_EXTRUDER
+ LOOP_L_N(n, EXTRUDERS)
+ EDIT_ITEM_FAST_N(float4, n, MSG_FILAMENTLOAD_E, &fc_settings[n].load_length, 0, extrude_maxlength);
+ #endif
+ #endif
+
+ #if HAS_FILAMENT_RUNOUT_DISTANCE
+ editable.decimal = runout.runout_distance();
+ EDIT_ITEM_FAST(float3, MSG_RUNOUT_DISTANCE_MM, &editable.decimal, 1, 999,
+ []{ runout.set_runout_distance(editable.decimal); }, true
+ );
+ #endif
+
+ END_MENU();
+ }
+
+#endif // !NO_VOLUMETRICS || ADVANCED_PAUSE_FEATURE
+
+//
+// Advanced Settings > Temperature helpers
+//
+
+#if ENABLED(PID_AUTOTUNE_MENU)
+
+ #if ENABLED(PIDTEMP)
+ int16_t autotune_temp[HOTENDS] = ARRAY_BY_HOTENDS1(PREHEAT_1_TEMP_HOTEND);
+ #endif
+ #if ENABLED(PIDTEMPBED)
+ int16_t autotune_temp_bed = PREHEAT_1_TEMP_BED;
+ #endif
+
+ #include "../../gcode/queue.h"
+
+ void _lcd_autotune(const int16_t e) {
+ char cmd[30];
+ sprintf_P(cmd, PSTR("M303 U1 E%i S%i"), e,
+ #if HAS_PID_FOR_BOTH
+ e < 0 ? autotune_temp_bed : autotune_temp[e]
+ #else
+ TERN(PIDTEMPBED, autotune_temp_bed, autotune_temp[e])
+ #endif
+ );
+ queue.inject(cmd);
+ ui.return_to_status();
+ }
+
+#endif // PID_AUTOTUNE_MENU
+
+#if ENABLED(PID_EDIT_MENU)
+
+ float raw_Ki, raw_Kd; // place-holders for Ki and Kd edits
+
+ // Helpers for editing PID Ki & Kd values
+ // grab the PID value out of the temp variable; scale it; then update the PID driver
+ void copy_and_scalePID_i(int16_t e) {
+ UNUSED(e);
+ PID_PARAM(Ki, e) = scalePID_i(raw_Ki);
+ thermalManager.updatePID();
+ }
+ void copy_and_scalePID_d(int16_t e) {
+ UNUSED(e);
+ PID_PARAM(Kd, e) = scalePID_d(raw_Kd);
+ thermalManager.updatePID();
+ }
+
+ #define _DEFINE_PIDTEMP_BASE_FUNCS(N) \
+ void copy_and_scalePID_i_E##N() { copy_and_scalePID_i(N); } \
+ void copy_and_scalePID_d_E##N() { copy_and_scalePID_d(N); }
+
+#else
+
+ #define _DEFINE_PIDTEMP_BASE_FUNCS(N) //
+
+#endif
+
+#if ENABLED(PID_AUTOTUNE_MENU)
+ #define DEFINE_PIDTEMP_FUNCS(N) \
+ _DEFINE_PIDTEMP_BASE_FUNCS(N); \
+ void lcd_autotune_callback_E##N() { _lcd_autotune(N); }
+#else
+ #define DEFINE_PIDTEMP_FUNCS(N) _DEFINE_PIDTEMP_BASE_FUNCS(N);
+#endif
+
+#if HAS_HOTEND
+ DEFINE_PIDTEMP_FUNCS(0);
+ #if ENABLED(PID_PARAMS_PER_HOTEND)
+ REPEAT_S(1, HOTENDS, DEFINE_PIDTEMP_FUNCS)
+ #endif
+#endif
+
+#if BOTH(AUTOTEMP, HAS_TEMP_HOTEND) || EITHER(PID_AUTOTUNE_MENU, PID_EDIT_MENU)
+ #define SHOW_MENU_ADVANCED_TEMPERATURE 1
+#endif
+
+//
+// Advanced Settings > Temperature
+//
+#if SHOW_MENU_ADVANCED_TEMPERATURE
+
+ void menu_advanced_temperature() {
+ START_MENU();
+ BACK_ITEM(MSG_ADVANCED_SETTINGS);
+ //
+ // Autotemp, Min, Max, Fact
+ //
+ #if BOTH(AUTOTEMP, HAS_TEMP_HOTEND)
+ EDIT_ITEM(bool, MSG_AUTOTEMP, &planner.autotemp_enabled);
+ EDIT_ITEM(float3, MSG_MIN, &planner.autotemp_min, 0, float(HEATER_0_MAXTEMP) - HOTEND_OVERSHOOT);
+ EDIT_ITEM(float3, MSG_MAX, &planner.autotemp_max, 0, float(HEATER_0_MAXTEMP) - HOTEND_OVERSHOOT);
+ EDIT_ITEM(float42_52, MSG_FACTOR, &planner.autotemp_factor, 0, 10);
+ #endif
+
+ //
+ // PID-P, PID-I, PID-D, PID-C, PID Autotune
+ // PID-P E1, PID-I E1, PID-D E1, PID-C E1, PID Autotune E1
+ // PID-P E2, PID-I E2, PID-D E2, PID-C E2, PID Autotune E2
+ // PID-P E3, PID-I E3, PID-D E3, PID-C E3, PID Autotune E3
+ // PID-P E4, PID-I E4, PID-D E4, PID-C E4, PID Autotune E4
+ // PID-P E5, PID-I E5, PID-D E5, PID-C E5, PID Autotune E5
+ //
+
+ #if ENABLED(PID_EDIT_MENU)
+ #define __PID_BASE_MENU_ITEMS(N) \
+ raw_Ki = unscalePID_i(TERN(PID_BED_MENU_SECTION, thermalManager.temp_bed.pid.Ki, PID_PARAM(Ki, N))); \
+ raw_Kd = unscalePID_d(TERN(PID_BED_MENU_SECTION, thermalManager.temp_bed.pid.Kd, PID_PARAM(Kd, N))); \
+ EDIT_ITEM_FAST_N(float41sign, N, MSG_PID_P_E, &TERN(PID_BED_MENU_SECTION, thermalManager.temp_bed.pid.Kp, PID_PARAM(Kp, N)), 1, 9990); \
+ EDIT_ITEM_FAST_N(float52sign, N, MSG_PID_I_E, &raw_Ki, 0.01f, 9990, []{ copy_and_scalePID_i(N); }); \
+ EDIT_ITEM_FAST_N(float41sign, N, MSG_PID_D_E, &raw_Kd, 1, 9990, []{ copy_and_scalePID_d(N); })
+
+ #if ENABLED(PID_EXTRUSION_SCALING)
+ #define _PID_BASE_MENU_ITEMS(N) \
+ __PID_BASE_MENU_ITEMS(N); \
+ EDIT_ITEM_N(float4, N, MSG_PID_C_E, &PID_PARAM(Kc, N), 1, 9990)
+ #else
+ #define _PID_BASE_MENU_ITEMS(N) __PID_BASE_MENU_ITEMS(N)
+ #endif
+
+ #if ENABLED(PID_FAN_SCALING)
+ #define _PID_EDIT_MENU_ITEMS(N) \
+ _PID_BASE_MENU_ITEMS(N); \
+ EDIT_ITEM_N(float4, N, MSG_PID_F_E, &PID_PARAM(Kf, N), 1, 9990)
+ #else
+ #define _PID_EDIT_MENU_ITEMS(N) _PID_BASE_MENU_ITEMS(N)
+ #endif
+
+ #else
+
+ #define _PID_EDIT_MENU_ITEMS(N) NOOP
+
+ #endif
+
+ #if ENABLED(PID_AUTOTUNE_MENU)
+ #define PID_EDIT_MENU_ITEMS(N) \
+ _PID_EDIT_MENU_ITEMS(N); \
+ EDIT_ITEM_FAST_N(int3, N, MSG_PID_AUTOTUNE_E, &autotune_temp[N], 150, thermalManager.heater_maxtemp[N] - HOTEND_OVERSHOOT, []{ _lcd_autotune(MenuItemBase::itemIndex); });
+ #else
+ #define PID_EDIT_MENU_ITEMS(N) _PID_EDIT_MENU_ITEMS(N);
+ #endif
+
+ PID_EDIT_MENU_ITEMS(0);
+ #if ENABLED(PID_PARAMS_PER_HOTEND)
+ REPEAT_S(1, HOTENDS, PID_EDIT_MENU_ITEMS)
+ #endif
+
+ #if ENABLED(PIDTEMPBED)
+ #if ENABLED(PID_EDIT_MENU)
+ #define PID_BED_MENU_SECTION
+ __PID_BASE_MENU_ITEMS(-1);
+ #undef PID_BED_MENU_SECTION
+ #endif
+ #if ENABLED(PID_AUTOTUNE_MENU)
+ EDIT_ITEM_FAST_N(int3, -1, MSG_PID_AUTOTUNE_E, &autotune_temp_bed, PREHEAT_1_TEMP_BED, BED_MAX_TARGET, []{ _lcd_autotune(-1); });
+ #endif
+ #endif
+
+ END_MENU();
+ }
+
+#endif // SHOW_MENU_ADVANCED_TEMPERATURE
+
+#if DISABLED(SLIM_LCD_MENUS)
+
+ #if ENABLED(DISTINCT_E_FACTORS)
+ inline void _reset_e_acceleration_rate(const uint8_t e) { if (e == active_extruder) planner.reset_acceleration_rates(); }
+ inline void _planner_refresh_e_positioning(const uint8_t e) {
+ if (e == active_extruder)
+ planner.refresh_positioning();
+ else
+ planner.steps_to_mm[E_AXIS_N(e)] = 1.0f / planner.settings.axis_steps_per_mm[E_AXIS_N(e)];
+ }
+ #endif
+
+ // M203 / M205 Velocity options
+ void menu_advanced_velocity() {
+ // M203 Max Feedrate
+ constexpr xyze_feedrate_t max_fr_edit =
+ #ifdef MAX_FEEDRATE_EDIT_VALUES
+ MAX_FEEDRATE_EDIT_VALUES
+ #elif ENABLED(LIMITED_MAX_FR_EDITING)
+ DEFAULT_MAX_FEEDRATE
+ #else
+ { 9999, 9999, 9999, 9999 }
+ #endif
+ ;
+ #if ENABLED(LIMITED_MAX_FR_EDITING) && !defined(MAX_FEEDRATE_EDIT_VALUES)
+ const xyze_feedrate_t max_fr_edit_scaled = max_fr_edit * 2;
+ #else
+ const xyze_feedrate_t &max_fr_edit_scaled = max_fr_edit;
+ #endif
+
+ START_MENU();
+ BACK_ITEM(MSG_ADVANCED_SETTINGS);
+
+ #define EDIT_VMAX(N) EDIT_ITEM_FAST(float5, MSG_VMAX_##N, &planner.settings.max_feedrate_mm_s[_AXIS(N)], 1, max_fr_edit_scaled[_AXIS(N)])
+ EDIT_VMAX(A);
+ EDIT_VMAX(B);
+ EDIT_VMAX(C);
+
+ #if E_STEPPERS
+ EDIT_ITEM_FAST(float5, MSG_VMAX_E, &planner.settings.max_feedrate_mm_s[E_AXIS_N(active_extruder)], 1, max_fr_edit_scaled.e);
+ #endif
+ #if ENABLED(DISTINCT_E_FACTORS)
+ LOOP_L_N(n, E_STEPPERS)
+ EDIT_ITEM_FAST_N(float5, n, MSG_VMAX_EN, &planner.settings.max_feedrate_mm_s[E_AXIS_N(n)], 1, max_fr_edit_scaled.e);
+ #endif
+
+ // M205 S Min Feedrate
+ EDIT_ITEM_FAST(float5, MSG_VMIN, &planner.settings.min_feedrate_mm_s, 0, 9999);
+
+ // M205 T Min Travel Feedrate
+ EDIT_ITEM_FAST(float5, MSG_VTRAV_MIN, &planner.settings.min_travel_feedrate_mm_s, 0, 9999);
+
+ END_MENU();
+ }
+
+ // M201 / M204 Accelerations
+ void menu_advanced_acceleration() {
+ const float max_accel = _MAX(planner.settings.max_acceleration_mm_per_s2[A_AXIS], planner.settings.max_acceleration_mm_per_s2[B_AXIS], planner.settings.max_acceleration_mm_per_s2[C_AXIS]);
+
+ // M201 settings
+ constexpr xyze_ulong_t max_accel_edit =
+ #ifdef MAX_ACCEL_EDIT_VALUES
+ MAX_ACCEL_EDIT_VALUES
+ #elif ENABLED(LIMITED_MAX_ACCEL_EDITING)
+ DEFAULT_MAX_ACCELERATION
+ #else
+ { 99000, 99000, 99000, 99000 }
+ #endif
+ ;
+ #if ENABLED(LIMITED_MAX_ACCEL_EDITING) && !defined(MAX_ACCEL_EDIT_VALUES)
+ const xyze_ulong_t max_accel_edit_scaled = max_accel_edit * 2;
+ #else
+ const xyze_ulong_t &max_accel_edit_scaled = max_accel_edit;
+ #endif
+
+ START_MENU();
+ BACK_ITEM(MSG_ADVANCED_SETTINGS);
+
+ // M204 P Acceleration
+ EDIT_ITEM_FAST(float5_25, MSG_ACC, &planner.settings.acceleration, 25, max_accel);
+
+ // M204 R Retract Acceleration
+ EDIT_ITEM_FAST(float5, MSG_A_RETRACT, &planner.settings.retract_acceleration, 100, planner.settings.max_acceleration_mm_per_s2[E_AXIS_N(active_extruder)]);
+
+ // M204 T Travel Acceleration
+ EDIT_ITEM_FAST(float5_25, MSG_A_TRAVEL, &planner.settings.travel_acceleration, 25, max_accel);
+
+ #define EDIT_AMAX(Q,L) EDIT_ITEM_FAST(long5_25, MSG_AMAX_##Q, &planner.settings.max_acceleration_mm_per_s2[_AXIS(Q)], L, max_accel_edit_scaled[_AXIS(Q)], []{ planner.reset_acceleration_rates(); })
+ EDIT_AMAX(A, 100);
+ EDIT_AMAX(B, 100);
+ EDIT_AMAX(C, 10);
+
+ #if ENABLED(DISTINCT_E_FACTORS)
+ EDIT_ITEM_FAST(long5_25, MSG_AMAX_E, &planner.settings.max_acceleration_mm_per_s2[E_AXIS_N(active_extruder)], 100, max_accel_edit_scaled.e, []{ planner.reset_acceleration_rates(); });
+ LOOP_L_N(n, E_STEPPERS)
+ EDIT_ITEM_FAST_N(long5_25, n, MSG_AMAX_EN, &planner.settings.max_acceleration_mm_per_s2[E_AXIS_N(n)], 100, max_accel_edit_scaled.e, []{ _reset_e_acceleration_rate(MenuItemBase::itemIndex); });
+ #elif E_STEPPERS
+ EDIT_ITEM_FAST(long5_25, MSG_AMAX_E, &planner.settings.max_acceleration_mm_per_s2[E_AXIS], 100, max_accel_edit_scaled.e, []{ planner.reset_acceleration_rates(); });
+ #endif
+
+ #ifdef XY_FREQUENCY_LIMIT
+ EDIT_ITEM(int8, MSG_XY_FREQUENCY_LIMIT, &planner.xy_freq_limit_hz, 0, 100, planner.refresh_frequency_limit, true);
+ editable.uint8 = uint8_t(LROUND(planner.xy_freq_min_speed_factor * 255)); // percent to u8
+ EDIT_ITEM(percent, MSG_XY_FREQUENCY_FEEDRATE, &editable.uint8, 3, 255, []{ planner.set_min_speed_factor_u8(editable.uint8); }, true);
+ #endif
+
+ END_MENU();
+ }
+
+ #if HAS_CLASSIC_JERK
+
+ void menu_advanced_jerk() {
+ START_MENU();
+ BACK_ITEM(MSG_ADVANCED_SETTINGS);
+
+ #if HAS_JUNCTION_DEVIATION
+ #if ENABLED(LIN_ADVANCE)
+ EDIT_ITEM(float43, MSG_JUNCTION_DEVIATION, &planner.junction_deviation_mm, 0.001f, 0.3f, planner.recalculate_max_e_jerk);
+ #else
+ EDIT_ITEM(float43, MSG_JUNCTION_DEVIATION, &planner.junction_deviation_mm, 0.001f, 0.5f);
+ #endif
+ #endif
+
+ constexpr xyze_float_t max_jerk_edit =
+ #ifdef MAX_JERK_EDIT_VALUES
+ MAX_JERK_EDIT_VALUES
+ #elif ENABLED(LIMITED_JERK_EDITING)
+ { (DEFAULT_XJERK) * 2, (DEFAULT_YJERK) * 2, (DEFAULT_ZJERK) * 2, (DEFAULT_EJERK) * 2 }
+ #else
+ { 990, 990, 990, 990 }
+ #endif
+ ;
+ #define EDIT_JERK(N) EDIT_ITEM_FAST(float3, MSG_V##N##_JERK, &planner.max_jerk[_AXIS(N)], 1, max_jerk_edit[_AXIS(N)])
+ EDIT_JERK(A);
+ EDIT_JERK(B);
+ #if ENABLED(DELTA)
+ EDIT_JERK(C);
+ #else
+ EDIT_ITEM_FAST(float52sign, MSG_VC_JERK, &planner.max_jerk.c, 0.1f, max_jerk_edit.c);
+ #endif
+ #if HAS_CLASSIC_E_JERK
+ EDIT_ITEM_FAST(float52sign, MSG_VE_JERK, &planner.max_jerk.e, 0.1f, max_jerk_edit.e);
+ #endif
+
+ END_MENU();
+ }
+
+ #endif
+
+ // M851 - Z Probe Offsets
+ #if HAS_BED_PROBE
+ void menu_probe_offsets() {
+ START_MENU();
+ BACK_ITEM(MSG_ADVANCED_SETTINGS);
+ #if HAS_PROBE_XY_OFFSET
+ EDIT_ITEM(float31sign, MSG_ZPROBE_XOFFSET, &probe.offset.x, -(X_BED_SIZE), X_BED_SIZE);
+ EDIT_ITEM(float31sign, MSG_ZPROBE_YOFFSET, &probe.offset.y, -(Y_BED_SIZE), Y_BED_SIZE);
+ #endif
+ EDIT_ITEM(LCD_Z_OFFSET_TYPE, MSG_ZPROBE_ZOFFSET, &probe.offset.z, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX);
+
+ #if ENABLED(PROBE_OFFSET_WIZARD)
+ SUBMENU(MSG_PROBE_WIZARD, goto_probe_offset_wizard);
+ #endif
+
+ END_MENU();
+ }
+ #endif
+
+#endif // !SLIM_LCD_MENUS
+
+// M92 Steps-per-mm
+void menu_advanced_steps_per_mm() {
+ START_MENU();
+ BACK_ITEM(MSG_ADVANCED_SETTINGS);
+
+ #define EDIT_QSTEPS(Q) EDIT_ITEM_FAST(float51, MSG_##Q##_STEPS, &planner.settings.axis_steps_per_mm[_AXIS(Q)], 5, 9999, []{ planner.refresh_positioning(); })
+ EDIT_QSTEPS(A);
+ EDIT_QSTEPS(B);
+ EDIT_QSTEPS(C);
+
+ #if ENABLED(DISTINCT_E_FACTORS)
+ LOOP_L_N(n, E_STEPPERS)
+ EDIT_ITEM_FAST_N(float51, n, MSG_EN_STEPS, &planner.settings.axis_steps_per_mm[E_AXIS_N(n)], 5, 9999, []{ _planner_refresh_e_positioning(MenuItemBase::itemIndex); });
+ #elif E_STEPPERS
+ EDIT_ITEM_FAST(float51, MSG_E_STEPS, &planner.settings.axis_steps_per_mm[E_AXIS], 5, 9999, []{ planner.refresh_positioning(); });
+ #endif
+
+ END_MENU();
+}
+
+void menu_advanced_settings() {
+ const bool is_busy = printer_busy();
+
+ #if ENABLED(SD_FIRMWARE_UPDATE)
+ bool sd_update_state = settings.sd_update_status();
+ #endif
+
+ START_MENU();
+ BACK_ITEM(MSG_CONFIGURATION);
+
+ #if DISABLED(SLIM_LCD_MENUS)
+
+ #if HAS_M206_COMMAND
+ //
+ // Set Home Offsets
+ //
+ ACTION_ITEM(MSG_SET_HOME_OFFSETS, []{ queue.inject_P(PSTR("M428")); ui.return_to_status(); });
+ #endif
+
+ // M203 / M205 - Feedrate items
+ SUBMENU(MSG_VELOCITY, menu_advanced_velocity);
+
+ // M201 - Acceleration items
+ SUBMENU(MSG_ACCELERATION, menu_advanced_acceleration);
+
+ #if HAS_CLASSIC_JERK
+ // M205 - Max Jerk
+ SUBMENU(MSG_JERK, menu_advanced_jerk);
+ #elif HAS_JUNCTION_DEVIATION
+ EDIT_ITEM(float43, MSG_JUNCTION_DEVIATION, &planner.junction_deviation_mm, 0.001f, 0.3f
+ #if ENABLED(LIN_ADVANCE)
+ , planner.recalculate_max_e_jerk
+ #endif
+ );
+ #endif
+
+ // M851 - Z Probe Offsets
+ #if HAS_BED_PROBE
+ if (!is_busy) SUBMENU(MSG_ZPROBE_OFFSETS, menu_probe_offsets);
+ #endif
+
+ #endif // !SLIM_LCD_MENUS
+
+ // M92 - Steps Per mm
+ if (!is_busy)
+ SUBMENU(MSG_STEPS_PER_MM, menu_advanced_steps_per_mm);
+
+ #if ENABLED(BACKLASH_GCODE)
+ SUBMENU(MSG_BACKLASH, menu_backlash);
+ #endif
+
+ #if ENABLED(HAS_MOTOR_CURRENT_DAC)
+ SUBMENU(MSG_DRIVE_STRENGTH, menu_dac);
+ #endif
+ #if HAS_MOTOR_CURRENT_PWM
+ SUBMENU(MSG_DRIVE_STRENGTH, menu_pwm);
+ #endif
+
+ #if HAS_TRINAMIC_CONFIG
+ SUBMENU(MSG_TMC_DRIVERS, menu_tmc);
+ #endif
+
+ #if SHOW_MENU_ADVANCED_TEMPERATURE
+ SUBMENU(MSG_TEMPERATURE, menu_advanced_temperature);
+ #endif
+
+ #if DISABLED(NO_VOLUMETRICS) || ENABLED(ADVANCED_PAUSE_FEATURE)
+ SUBMENU(MSG_FILAMENT, menu_advanced_filament);
+ #elif ENABLED(LIN_ADVANCE)
+ #if EXTRUDERS == 1
+ EDIT_ITEM(float42_52, MSG_ADVANCE_K, &planner.extruder_advance_K[0], 0, 10);
+ #elif HAS_MULTI_EXTRUDER
+ LOOP_L_N(n, E_STEPPERS)
+ EDIT_ITEM_N(float42_52, n, MSG_ADVANCE_K_E, &planner.extruder_advance_K[n], 0, 10);
+ #endif
+ #endif
+
+ // M540 S - Abort on endstop hit when SD printing
+ #if ENABLED(SD_ABORT_ON_ENDSTOP_HIT)
+ EDIT_ITEM(bool, MSG_ENDSTOP_ABORT, &planner.abort_on_endstop_hit);
+ #endif
+
+ #if ENABLED(SD_FIRMWARE_UPDATE)
+ EDIT_ITEM(bool, MSG_MEDIA_UPDATE, &sd_update_state, []{
+ //
+ // Toggle the SD Firmware Update state in EEPROM
+ //
+ const bool new_state = !settings.sd_update_status(),
+ didset = settings.set_sd_update_status(new_state);
+ ui.completion_feedback(didset);
+ ui.return_to_status();
+ if (new_state) LCD_MESSAGEPGM(MSG_RESET_PRINTER); else ui.reset_status();
+ });
+ #endif
+
+ #if ENABLED(PASSWORD_FEATURE)
+ SUBMENU(MSG_PASSWORD_SETTINGS, password.access_menu_password);
+ #endif
+
+ #if ENABLED(EEPROM_SETTINGS) && DISABLED(SLIM_LCD_MENUS)
+ CONFIRM_ITEM(MSG_INIT_EEPROM,
+ MSG_BUTTON_INIT, MSG_BUTTON_CANCEL,
+ ui.init_eeprom, nullptr,
+ GET_TEXT(MSG_INIT_EEPROM), (const char *)nullptr, PSTR("?")
+ );
+ #endif
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU
diff --git a/Marlin/src/lcd/menu/menu_backlash.cpp b/Marlin/src/lcd/menu/menu_backlash.cpp
new file mode 100644
index 0000000..9d0b970
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_backlash.cpp
@@ -0,0 +1,53 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Backlash Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, BACKLASH_GCODE)
+
+#include "menu_item.h"
+
+#include "../../feature/backlash.h"
+
+void menu_backlash() {
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ EDIT_ITEM_FAST(percent, MSG_BACKLASH_CORRECTION, &backlash.correction, all_off, all_on);
+
+ #define EDIT_BACKLASH_DISTANCE(N) EDIT_ITEM_FAST(float43, MSG_BACKLASH_##N, &backlash.distance_mm[_AXIS(N)], 0.0f, 9.9f);
+ if (AXIS_CAN_CALIBRATE(A)) EDIT_BACKLASH_DISTANCE(A);
+ if (AXIS_CAN_CALIBRATE(B)) EDIT_BACKLASH_DISTANCE(B);
+ if (AXIS_CAN_CALIBRATE(C)) EDIT_BACKLASH_DISTANCE(C);
+
+ #ifdef BACKLASH_SMOOTHING_MM
+ EDIT_ITEM_FAST(float43, MSG_BACKLASH_SMOOTHING, &backlash.smoothing_mm, 0.0f, 9.9f);
+ #endif
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && BACKLASH_GCODE
diff --git a/Marlin/src/lcd/menu/menu_bed_corners.cpp b/Marlin/src/lcd/menu/menu_bed_corners.cpp
new file mode 100644
index 0000000..751be18
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_bed_corners.cpp
@@ -0,0 +1,361 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Level Bed Corners menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, LEVEL_BED_CORNERS)
+
+#include "menu_item.h"
+#include "../../module/motion.h"
+#include "../../module/planner.h"
+
+#if HAS_LEVELING
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+#ifndef LEVEL_CORNERS_Z_HOP
+ #define LEVEL_CORNERS_Z_HOP 4.0
+#endif
+#ifndef LEVEL_CORNERS_HEIGHT
+ #define LEVEL_CORNERS_HEIGHT 0.0
+#endif
+
+#if ENABLED(LEVEL_CORNERS_USE_PROBE)
+ #include "../../module/probe.h"
+ #include "../../module/endstops.h"
+ #if ENABLED(BLTOUCH)
+ #include "../../feature/bltouch.h"
+ #endif
+ #ifndef LEVEL_CORNERS_PROBE_TOLERANCE
+ #define LEVEL_CORNERS_PROBE_TOLERANCE 0.2
+ #endif
+ float last_z;
+ int good_points;
+ bool corner_probing_done, wait_for_probe;
+
+ #if HAS_MARLINUI_U8GLIB
+ #include "../dogm/marlinui_DOGM.h"
+ #endif
+ #define GOOD_POINTS_TO_STR(N) ui8tostr2(N)
+ #define LAST_Z_TO_STR(N) ftostr53_63(N) //ftostr42_52(N)
+
+#endif
+
+static_assert(LEVEL_CORNERS_Z_HOP >= 0, "LEVEL_CORNERS_Z_HOP must be >= 0. Please update your configuration.");
+
+#if HAS_LEVELING
+ static bool leveling_was_active = false;
+#endif
+
+#ifndef LEVEL_CORNERS_LEVELING_ORDER
+ #define LEVEL_CORNERS_LEVELING_ORDER { LF, RF, LB, RB } // Default
+ //#define LEVEL_CORNERS_LEVELING_ORDER { LF, LB, RF } // 3 hard-coded points
+ //#define LEVEL_CORNERS_LEVELING_ORDER { LF, RF } // 3-Point tramming - Rear
+ //#define LEVEL_CORNERS_LEVELING_ORDER { LF, LB } // 3-Point tramming - Right
+ //#define LEVEL_CORNERS_LEVELING_ORDER { RF, RB } // 3-Point tramming - Left
+ //#define LEVEL_CORNERS_LEVELING_ORDER { LB, RB } // 3-Point tramming - Front
+#endif
+
+#define LF 1
+#define RF 2
+#define RB 3
+#define LB 4
+constexpr int lco[] = LEVEL_CORNERS_LEVELING_ORDER;
+constexpr bool level_corners_3_points = COUNT(lco) == 2;
+static_assert(level_corners_3_points || COUNT(lco) == 4, "LEVEL_CORNERS_LEVELING_ORDER must have exactly 2 or 4 corners.");
+
+constexpr int lcodiff = abs(lco[0] - lco[1]);
+static_assert(COUNT(lco) == 4 || lcodiff == 1 || lcodiff == 3, "The first two LEVEL_CORNERS_LEVELING_ORDER corners must be on the same edge.");
+
+constexpr int nr_edge_points = level_corners_3_points ? 3 : 4;
+constexpr int available_points = nr_edge_points + ENABLED(LEVEL_CENTER_TOO);
+constexpr int center_index = TERN(LEVEL_CENTER_TOO, available_points - 1, -1);
+constexpr float inset_lfrb[4] = LEVEL_CORNERS_INSET_LFRB;
+constexpr xy_pos_t lf { (X_MIN_BED) + inset_lfrb[0], (Y_MIN_BED) + inset_lfrb[1] },
+ rb { (X_MAX_BED) - inset_lfrb[2], (Y_MAX_BED) - inset_lfrb[3] };
+
+static int8_t bed_corner;
+
+/**
+ * Select next corner coordinates
+ */
+static inline void _lcd_level_bed_corners_get_next_position() {
+
+ if (level_corners_3_points) {
+ if (bed_corner >= available_points) bed_corner = 0; // Above max position -> move back to first corner
+ switch (bed_corner) {
+ case 0 ... 1:
+ // First two corners set explicitly by the configuration
+ current_position = lf; // Left front
+ switch (lco[bed_corner]) {
+ case RF: current_position.x = rb.x; break; // Right Front
+ case RB: current_position = rb; break; // Right Back
+ case LB: current_position.y = rb.y; break; // Left Back
+ }
+ break;
+
+ case 2:
+ // Determine which edge to probe for 3rd point
+ current_position.set(lf.x + (rb.x - lf.x) / 2, lf.y + (rb.y - lf.y) / 2);
+ if ((lco[0] == LB && lco[1] == RB) || (lco[0] == RB && lco[1] == LB)) current_position.y = lf.y; // Front Center
+ if ((lco[0] == LF && lco[1] == LB) || (lco[0] == LB && lco[1] == LF)) current_position.x = rb.x; // Center Right
+ if ((lco[0] == RF && lco[1] == RB) || (lco[0] == RB && lco[1] == RF)) current_position.x = lf.x; // Left Center
+ if ((lco[0] == LF && lco[1] == RF) || (lco[0] == RF && lco[1] == LF)) current_position.y = rb.y; // Center Back
+ #if DISABLED(LEVEL_CENTER_TOO) && ENABLED(LEVEL_CORNERS_USE_PROBE)
+ bed_corner++; // Must increment the count to ensure it resets the loop if the 3rd point is out of tolerance
+ #endif
+ break;
+
+ #if ENABLED(LEVEL_CENTER_TOO)
+ case 3:
+ current_position.set(X_CENTER, Y_CENTER);
+ break;
+ #endif
+ }
+ }
+ else {
+ // Four-Corner Bed Tramming with optional center
+ if (TERN0(LEVEL_CENTER_TOO, bed_corner == center_index)) {
+ current_position.set(X_CENTER, Y_CENTER);
+ TERN_(LEVEL_CORNERS_USE_PROBE, good_points--); // Decrement to allow one additional probe point
+ }
+ else {
+ current_position = lf; // Left front
+ switch (lco[bed_corner]) {
+ case RF: current_position.x = rb.x; break; // Right front
+ case RB: current_position = rb; break; // Right rear
+ case LB: current_position.y = rb.y; break; // Left rear
+ }
+ }
+ }
+}
+
+/**
+ * Level corners, starting in the front-left corner.
+ */
+#if ENABLED(LEVEL_CORNERS_USE_PROBE)
+
+ #define VALIDATE_POINT(X, Y, STR) static_assert(Probe::build_time::can_reach((X), (Y)), \
+ "LEVEL_CORNERS_INSET_LFRB " STR " inset is not reachable with the default NOZZLE_TO_PROBE offset and PROBING_MARGIN.")
+ VALIDATE_POINT(lf.x, Y_CENTER, "left"); VALIDATE_POINT(X_CENTER, lf.y, "front");
+ VALIDATE_POINT(rb.x, Y_CENTER, "right"); VALIDATE_POINT(X_CENTER, rb.y, "back");
+
+ #ifndef PAGE_CONTAINS
+ #define PAGE_CONTAINS(...) true
+ #endif
+
+ void _lcd_draw_probing() {
+ if (!ui.should_draw()) return;
+
+ TERN_(HAS_MARLINUI_U8GLIB, ui.set_font(FONT_MENU)); // Set up the font for extra info
+
+ MenuItem_static::draw(0, GET_TEXT(MSG_PROBING_MESH), SS_INVERT); // "Probing Mesh" heading
+
+ uint8_t cy = TERN(TFT_COLOR_UI, 3, LCD_HEIGHT - 1), y = LCD_ROW_Y(cy);
+
+ // Display # of good points found vs total needed
+ if (PAGE_CONTAINS(y - (MENU_FONT_HEIGHT), y)) {
+ SETCURSOR(TERN(TFT_COLOR_UI, 2, 0), cy);
+ lcd_put_u8str_P(GET_TEXT(MSG_LEVEL_CORNERS_GOOD_POINTS));
+ IF_ENABLED(TFT_COLOR_UI, lcd_moveto(12, cy));
+ lcd_put_u8str(GOOD_POINTS_TO_STR(good_points));
+ lcd_put_wchar('/');
+ lcd_put_u8str(GOOD_POINTS_TO_STR(nr_edge_points));
+ }
+
+ --cy;
+ y -= MENU_FONT_HEIGHT;
+
+ // Display the Last Z value
+ if (PAGE_CONTAINS(y - (MENU_FONT_HEIGHT), y)) {
+ SETCURSOR(TERN(TFT_COLOR_UI, 2, 0), cy);
+ lcd_put_u8str_P(GET_TEXT(MSG_LEVEL_CORNERS_LAST_Z));
+ IF_ENABLED(TFT_COLOR_UI, lcd_moveto(12, 2));
+ lcd_put_u8str(LAST_Z_TO_STR(last_z));
+ }
+ }
+
+ void _lcd_draw_raise() {
+ if (!ui.should_draw()) return;
+ MenuItem_confirm::select_screen(
+ GET_TEXT(MSG_BUTTON_DONE), GET_TEXT(MSG_BUTTON_SKIP)
+ , []{ corner_probing_done = true; wait_for_probe = false; }
+ , []{ wait_for_probe = false; }
+ , GET_TEXT(MSG_LEVEL_CORNERS_RAISE)
+ , (const char*)nullptr, NUL_STR
+ );
+ }
+
+ void _lcd_draw_level_prompt() {
+ if (!ui.should_draw()) return;
+ MenuItem_confirm::confirm_screen(
+ []{ queue.inject_P(TERN(HAS_LEVELING, PSTR("G29N"), G28_STR));
+ ui.return_to_status();
+ }
+ , []{ ui.goto_previous_screen_no_defer(); }
+ , GET_TEXT(MSG_LEVEL_CORNERS_IN_RANGE)
+ , (const char*)nullptr, PSTR("?")
+ );
+ }
+
+ bool _lcd_level_bed_corners_probe(bool verify=false) {
+ if (verify) do_blocking_move_to_z(current_position.z + LEVEL_CORNERS_Z_HOP); // do clearance if needed
+ TERN_(BLTOUCH_SLOW_MODE, bltouch.deploy()); // Deploy in LOW SPEED MODE on every probe action
+ do_blocking_move_to_z(last_z - LEVEL_CORNERS_PROBE_TOLERANCE, MMM_TO_MMS(Z_PROBE_SPEED_SLOW)); // Move down to lower tolerance
+ if (TEST(endstops.trigger_state(), TERN(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN, Z_MIN, Z_MIN_PROBE))) { // check if probe triggered
+ endstops.hit_on_purpose();
+ set_current_from_steppers_for_axis(Z_AXIS);
+ sync_plan_position();
+ TERN_(BLTOUCH_SLOW_MODE, bltouch.stow()); // Stow in LOW SPEED MODE on every trigger
+ // Triggered outside tolerance range?
+ if (ABS(current_position.z - last_z) > LEVEL_CORNERS_PROBE_TOLERANCE) {
+ last_z = current_position.z; // Above tolerance. Set a new Z for subsequent corners.
+ good_points = 0; // ...and start over
+ }
+ return true; // probe triggered
+ }
+ do_blocking_move_to_z(last_z); // go back to tolerance middle point before raise
+ return false; // probe not triggered
+ }
+
+ bool _lcd_level_bed_corners_raise() {
+ bool probe_triggered = false;
+ corner_probing_done = false;
+ wait_for_probe = true;
+ ui.goto_screen(_lcd_draw_raise); // show raise screen
+ ui.set_selection(true);
+ while (wait_for_probe && !probe_triggered) { // loop while waiting to bed raise and probe trigger
+ probe_triggered = PROBE_TRIGGERED();
+ if (probe_triggered) {
+ endstops.hit_on_purpose();
+ TERN_(LEVEL_CORNERS_AUDIO_FEEDBACK, ui.buzz(200, 600));
+ }
+ idle();
+ }
+ TERN_(BLTOUCH_SLOW_MODE, bltouch.stow());
+ ui.goto_screen(_lcd_draw_probing);
+ return (probe_triggered);
+ }
+
+ void _lcd_test_corners() {
+ bed_corner = TERN(LEVEL_CENTER_TOO, center_index, 0);
+ last_z = LEVEL_CORNERS_HEIGHT;
+ endstops.enable_z_probe(true);
+ good_points = 0;
+ ui.goto_screen(_lcd_draw_probing);
+ do {
+ ui.refresh(LCDVIEW_REDRAW_NOW);
+ _lcd_draw_probing(); // update screen with # of good points
+ do_blocking_move_to_z(current_position.z + LEVEL_CORNERS_Z_HOP); // clearance
+
+ _lcd_level_bed_corners_get_next_position(); // Select next corner coordinates
+ current_position -= probe.offset_xy; // Account for probe offsets
+ do_blocking_move_to_xy(current_position); // Goto corner
+
+ if (!_lcd_level_bed_corners_probe()) { // Probe down to tolerance
+ if (_lcd_level_bed_corners_raise()) { // Prompt user to raise bed if needed
+ #if ENABLED(LEVEL_CORNERS_VERIFY_RAISED) // Verify
+ while (!_lcd_level_bed_corners_probe(true)) { // Loop while corner verified
+ if (!_lcd_level_bed_corners_raise()) { // Prompt user to raise bed if needed
+ if (corner_probing_done) return; // Done was selected
+ break; // Skip was selected
+ }
+ }
+ #endif
+ }
+ else if (corner_probing_done) // Done was selected
+ return;
+ }
+
+ if (bed_corner != center_index) good_points++; // ignore center
+ if (++bed_corner > 3) bed_corner = 0;
+
+ } while (good_points < nr_edge_points); // loop until all points within tolerance
+
+ ui.goto_screen(_lcd_draw_level_prompt); // prompt for bed leveling
+ ui.set_selection(true);
+ }
+
+#else // !LEVEL_CORNERS_USE_PROBE
+
+ static inline void _lcd_goto_next_corner() {
+ line_to_z(LEVEL_CORNERS_Z_HOP);
+
+ // Select next corner coordinates
+ _lcd_level_bed_corners_get_next_position();
+
+ line_to_current_position(manual_feedrate_mm_s.x);
+ line_to_z(LEVEL_CORNERS_HEIGHT);
+ if (++bed_corner >= available_points) bed_corner = 0;
+ }
+
+#endif // !LEVEL_CORNERS_USE_PROBE
+
+static inline void _lcd_level_bed_corners_homing() {
+ _lcd_draw_homing();
+ if (!all_axes_homed()) return;
+ #if ENABLED(LEVEL_CORNERS_USE_PROBE)
+ _lcd_test_corners();
+ if (corner_probing_done) ui.goto_previous_screen_no_defer();
+ TERN_(HAS_LEVELING, set_bed_leveling_enabled(leveling_was_active));
+ endstops.enable_z_probe(false);
+ #else
+ bed_corner = 0;
+ ui.goto_screen([]{
+ MenuItem_confirm::select_screen(
+ GET_TEXT(MSG_BUTTON_NEXT), GET_TEXT(MSG_BUTTON_DONE)
+ , _lcd_goto_next_corner
+ , []{
+ line_to_z(LEVEL_CORNERS_Z_HOP); // Raise Z off the bed when done
+ TERN_(HAS_LEVELING, set_bed_leveling_enabled(leveling_was_active));
+ ui.goto_previous_screen_no_defer();
+ }
+ , GET_TEXT(TERN(LEVEL_CENTER_TOO, MSG_LEVEL_BED_NEXT_POINT, MSG_NEXT_CORNER))
+ , (const char*)nullptr, PSTR("?")
+ );
+ });
+ ui.set_selection(true);
+ _lcd_goto_next_corner();
+ #endif
+}
+
+void _lcd_level_bed_corners() {
+ ui.defer_status_screen();
+ if (!all_axes_trusted()) {
+ set_all_unhomed();
+ queue.inject_P(G28_STR);
+ }
+
+ // Disable leveling so the planner won't mess with us
+ #if HAS_LEVELING
+ leveling_was_active = planner.leveling_active;
+ set_bed_leveling_enabled(false);
+ #endif
+
+ 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
new file mode 100644
index 0000000..6a9f89f
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_bed_leveling.cpp
@@ -0,0 +1,301 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Bed Leveling Menus
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(LCD_BED_LEVELING)
+
+#include "menu_item.h"
+#include "../../module/planner.h"
+#include "../../feature/bedlevel/bedlevel.h"
+
+#if HAS_BED_PROBE && DISABLED(BABYSTEP_ZPROBE_OFFSET)
+ #include "../../module/probe.h"
+#endif
+
+#if HAS_GRAPHICAL_TFT
+ #include "../tft/tft.h"
+ #if ENABLED(TOUCH_SCREEN)
+ #include "../tft/touch.h"
+ #endif
+#endif
+
+#if EITHER(PROBE_MANUALLY, MESH_BED_LEVELING)
+
+ #include "../../module/motion.h"
+ #include "../../gcode/queue.h"
+
+ //
+ // Motion > Level Bed handlers
+ //
+
+ static uint8_t manual_probe_index;
+
+ // LCD probed points are from defaults
+ constexpr uint8_t total_probe_points = TERN(AUTO_BED_LEVELING_3POINT, 3, GRID_MAX_POINTS);
+
+ //
+ // Bed leveling is done. Wait for G29 to complete.
+ // A flag is used so that this can release control
+ // and allow the command queue to be processed.
+ //
+ // When G29 finishes the last move:
+ // - Raise Z to the "manual probe height"
+ // - Don't return until done.
+ //
+ // ** This blocks the command queue! **
+ //
+ void _lcd_level_bed_done() {
+ if (!ui.wait_for_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);
+ ui.synchronize(GET_TEXT(MSG_LEVEL_BED_DONE));
+ #endif
+ ui.goto_previous_screen_no_defer();
+ ui.completion_feedback();
+ }
+ if (ui.should_draw()) MenuItem_static::draw(LCD_HEIGHT >= 4, GET_TEXT(MSG_LEVEL_BED_DONE));
+ ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
+ }
+
+ void _lcd_level_goto_next_point();
+
+ //
+ // Step 7: Get the Z coordinate, click goes to the next point or exits
+ //
+ void _lcd_level_bed_get_z() {
+
+ if (ui.use_click()) {
+
+ //
+ // Save the current Z position and move
+ //
+
+ // If done...
+ if (++manual_probe_index >= total_probe_points) {
+ //
+ // The last G29 records the point and enables bed leveling
+ //
+ ui.wait_for_move = true;
+ ui.goto_screen(_lcd_level_bed_done);
+ #if ENABLED(MESH_BED_LEVELING)
+ queue.inject_P(PSTR("G29S2"));
+ #elif ENABLED(PROBE_MANUALLY)
+ queue.inject_P(PSTR("G29V1"));
+ #endif
+ }
+ else
+ _lcd_level_goto_next_point();
+
+ return;
+ }
+
+ //
+ // Encoder knob or keypad buttons adjust the Z position
+ //
+ if (ui.encoderPosition) {
+ const float z = current_position.z + 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));
+ ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
+ ui.encoderPosition = 0;
+ }
+
+ //
+ // Draw on first display, then only on Z change
+ //
+ if (ui.should_draw()) {
+ const float v = current_position.z;
+ MenuEditItemBase::draw_edit_screen(GET_TEXT(MSG_MOVE_Z), ftostr43sign(v + (v < 0 ? -0.0001f : 0.0001f), '+'));
+ }
+ }
+
+ //
+ // Step 6: Display "Next point: 1 / 9" while waiting for move to finish
+ //
+ void _lcd_level_bed_moving() {
+ if (ui.should_draw()) {
+ char msg[10];
+ sprintf_P(msg, PSTR("%i / %u"), int(manual_probe_index + 1), total_probe_points);
+ MenuEditItemBase::draw_edit_screen(GET_TEXT(MSG_LEVEL_BED_NEXT_POINT), msg);
+ }
+ ui.refresh(LCDVIEW_CALL_NO_REDRAW);
+ if (!ui.wait_for_move) ui.goto_screen(_lcd_level_bed_get_z);
+ }
+
+ //
+ // Step 5: Initiate a move to the next point
+ //
+ void _lcd_level_goto_next_point() {
+ ui.goto_screen(_lcd_level_bed_moving);
+
+ // G29 Records Z, moves, and signals when it pauses
+ ui.wait_for_move = true;
+ #if ENABLED(MESH_BED_LEVELING)
+ queue.inject_P(manual_probe_index ? PSTR("G29S2") : PSTR("G29S1"));
+ #elif ENABLED(PROBE_MANUALLY)
+ queue.inject_P(PSTR("G29V1"));
+ #endif
+ }
+
+ //
+ // Step 4: Display "Click to Begin", wait for click
+ // Move to the first probe position
+ //
+ void _lcd_level_bed_homing_done() {
+ if (ui.should_draw()) {
+ MenuItem_static::draw(1, GET_TEXT(MSG_LEVEL_BED_WAITING));
+ // Color UI needs a control to detect a touch
+ #if BOTH(TOUCH_SCREEN, HAS_GRAPHICAL_TFT)
+ touch.add_control(CLICK, 0, 0, TFT_WIDTH, TFT_HEIGHT);
+ #endif
+ }
+ if (ui.use_click()) {
+ manual_probe_index = 0;
+ _lcd_level_goto_next_point();
+ }
+ }
+
+ //
+ // Step 3: Display "Homing XYZ" - Wait for homing to finish
+ //
+ void _lcd_level_bed_homing() {
+ _lcd_draw_homing();
+ if (all_axes_homed()) ui.goto_screen(_lcd_level_bed_homing_done);
+ }
+
+ #if ENABLED(PROBE_MANUALLY)
+ extern bool g29_in_progress;
+ #endif
+
+ //
+ // Step 2: Continue Bed Leveling...
+ //
+ void _lcd_level_bed_continue() {
+ ui.defer_status_screen();
+ set_all_unhomed();
+ ui.goto_screen(_lcd_level_bed_homing);
+ queue.inject_P(G28_STR);
+ }
+
+#endif // PROBE_MANUALLY || MESH_BED_LEVELING
+
+#if ENABLED(MESH_EDIT_MENU)
+
+ inline void refresh_planner() {
+ set_current_from_steppers_for_axis(ALL_AXES);
+ sync_plan_position();
+ }
+
+ void menu_edit_mesh() {
+ static uint8_t xind, yind; // =0
+ START_MENU();
+ BACK_ITEM(MSG_BED_LEVELING);
+ EDIT_ITEM(uint8, MSG_MESH_X, &xind, 0, GRID_MAX_POINTS_X - 1);
+ EDIT_ITEM(uint8, MSG_MESH_Y, &yind, 0, GRID_MAX_POINTS_Y - 1);
+ EDIT_ITEM_FAST(float43, MSG_MESH_EDIT_Z, &Z_VALUES(xind, yind), -(LCD_PROBE_Z_RANGE) * 0.5, (LCD_PROBE_Z_RANGE) * 0.5, refresh_planner);
+ END_MENU();
+ }
+
+#endif // MESH_EDIT_MENU
+
+/**
+ * Step 1: Bed Level entry-point
+ *
+ * << Motion
+ * Auto Home (if homing needed)
+ * Leveling On/Off (if data exists, and homed)
+ * Fade Height: --- (Req: ENABLE_LEVELING_FADE_HEIGHT)
+ * Mesh Z Offset: --- (Req: MESH_BED_LEVELING)
+ * Z Probe Offset: --- (Req: HAS_BED_PROBE, Opt: BABYSTEP_ZPROBE_OFFSET)
+ * Level Bed >
+ * Level Corners > (if homed)
+ * Load Settings (Req: EEPROM_SETTINGS)
+ * Save Settings (Req: EEPROM_SETTINGS)
+ */
+void menu_bed_leveling() {
+ const bool is_homed = all_axes_trusted(),
+ is_valid = leveling_is_valid();
+
+ START_MENU();
+ BACK_ITEM(MSG_MOTION);
+
+ // Auto Home if not using manual probing
+ #if NONE(PROBE_MANUALLY, MESH_BED_LEVELING)
+ if (!is_homed) GCODES_ITEM(MSG_AUTO_HOME, G28_STR);
+ #endif
+
+ // Level Bed
+ #if EITHER(PROBE_MANUALLY, MESH_BED_LEVELING)
+ // Manual leveling uses a guided procedure
+ SUBMENU(MSG_LEVEL_BED, _lcd_level_bed_continue);
+ #else
+ // Automatic leveling can just run the G-code
+ GCODES_ITEM(MSG_LEVEL_BED, is_homed ? PSTR("G29") : PSTR("G29N"));
+ #endif
+
+ #if ENABLED(MESH_EDIT_MENU)
+ if (is_valid) SUBMENU(MSG_EDIT_MESH, menu_edit_mesh);
+ #endif
+
+ // Homed and leveling is valid? Then leveling can be toggled.
+ if (is_homed && is_valid) {
+ bool show_state = planner.leveling_active;
+ EDIT_ITEM(bool, MSG_BED_LEVELING, &show_state, _lcd_toggle_bed_leveling);
+ }
+
+ // Z Fade Height
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ // Shadow for editing the fade height
+ editable.decimal = planner.z_fade_height;
+ EDIT_ITEM_FAST(float3, MSG_Z_FADE_HEIGHT, &editable.decimal, 0, 100, []{ set_z_fade_height(editable.decimal); });
+ #endif
+
+ //
+ // Mesh Bed Leveling Z-Offset
+ //
+ #if ENABLED(MESH_BED_LEVELING)
+ EDIT_ITEM(float43, MSG_BED_Z, &mbl.z_offset, -1, 1);
+ #endif
+
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ SUBMENU(MSG_ZPROBE_ZOFFSET, lcd_babystep_zoffset);
+ #elif HAS_BED_PROBE
+ EDIT_ITEM(LCD_Z_OFFSET_TYPE, MSG_ZPROBE_ZOFFSET, &probe.offset.z, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX);
+ #endif
+
+ #if ENABLED(LEVEL_BED_CORNERS)
+ SUBMENU(MSG_LEVEL_CORNERS, _lcd_level_bed_corners);
+ #endif
+
+ #if ENABLED(EEPROM_SETTINGS)
+ ACTION_ITEM(MSG_LOAD_EEPROM, ui.load_settings);
+ ACTION_ITEM(MSG_STORE_EEPROM, ui.store_settings);
+ #endif
+ END_MENU();
+}
+
+#endif // LCD_BED_LEVELING
diff --git a/Marlin/src/lcd/menu/menu_cancelobject.cpp b/Marlin/src/lcd/menu/menu_cancelobject.cpp
new file mode 100644
index 0000000..a8ced05
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_cancelobject.cpp
@@ -0,0 +1,74 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Cancel Object Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, CANCEL_OBJECTS)
+
+#include "menu_item.h"
+#include "menu_addon.h"
+
+#include "../../feature/cancel_object.h"
+
+static void lcd_cancel_object_confirm() {
+ const int8_t v = MenuItemBase::itemIndex;
+ const char item_num[] = {
+ ' ',
+ char((v > 9) ? '0' + (v / 10) : ' '),
+ char('0' + (v % 10)),
+ '\0'
+ };
+ MenuItem_confirm::confirm_screen(
+ []{
+ cancelable.cancel_object(MenuItemBase::itemIndex);
+ ui.completion_feedback();
+ ui.goto_previous_screen();
+ },
+ ui.goto_previous_screen,
+ GET_TEXT(MSG_CANCEL_OBJECT), item_num, PSTR("?")
+ );
+}
+
+void menu_cancelobject() {
+ const int8_t ao = cancelable.active_object;
+
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ // Draw cancelable items in a loop
+ for (int8_t i = -1; i < cancelable.object_count; i++) {
+ if (i == ao) continue; // Active is drawn on -1 index
+ const int8_t j = i < 0 ? ao : i; // Active or index item
+ if (!cancelable.is_canceled(j)) { // Not canceled already?
+ SUBMENU_N(j, MSG_CANCEL_OBJECT_N, lcd_cancel_object_confirm); // Offer the option.
+ if (i < 0) SKIP_ITEM(); // Extra line after active
+ }
+ }
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && CANCEL_OBJECTS
diff --git a/Marlin/src/lcd/menu/menu_configuration.cpp b/Marlin/src/lcd/menu/menu_configuration.cpp
new file mode 100644
index 0000000..7b95f43
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_configuration.cpp
@@ -0,0 +1,436 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Configuration Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_LCD_MENU
+
+#include "menu_item.h"
+
+#if HAS_FILAMENT_SENSOR
+ #include "../../feature/runout.h"
+#endif
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+ #include "../../feature/powerloss.h"
+#endif
+
+#if HAS_BED_PROBE
+ #include "../../module/probe.h"
+ #if ENABLED(BLTOUCH)
+ #include "../../feature/bltouch.h"
+ #endif
+#endif
+
+#if ENABLED(SOUND_MENU_ITEM)
+ #include "../../libs/buzzer.h"
+#endif
+
+#define HAS_DEBUG_MENU ENABLED(LCD_PROGRESS_BAR_TEST)
+
+void menu_advanced_settings();
+#if EITHER(DELTA_CALIBRATION_MENU, DELTA_AUTO_CALIBRATION)
+ void menu_delta_calibrate();
+#endif
+
+#if ENABLED(LCD_PROGRESS_BAR_TEST)
+
+ #include "../lcdprint.h"
+
+ static void progress_bar_test() {
+ static int8_t bar_percent = 0;
+ if (ui.use_click()) {
+ ui.goto_previous_screen();
+ #if HAS_MARLINUI_HD44780
+ ui.set_custom_characters(CHARSET_MENU);
+ #endif
+ return;
+ }
+ bar_percent += (int8_t)ui.encoderPosition;
+ LIMIT(bar_percent, 0, 100);
+ ui.encoderPosition = 0;
+ MenuItem_static::draw(0, GET_TEXT(MSG_PROGRESS_BAR_TEST), SS_DEFAULT|SS_INVERT);
+ lcd_put_int((LCD_WIDTH) / 2 - 2, LCD_HEIGHT - 2, bar_percent); lcd_put_wchar('%');
+ lcd_moveto(0, LCD_HEIGHT - 1); ui.draw_progress_bar(bar_percent);
+ }
+
+ void _progress_bar_test() {
+ ui.goto_screen(progress_bar_test);
+ #if HAS_MARLINUI_HD44780
+ ui.set_custom_characters(CHARSET_INFO);
+ #endif
+ }
+
+#endif // LCD_PROGRESS_BAR_TEST
+
+#if HAS_DEBUG_MENU
+
+ void menu_debug() {
+ START_MENU();
+
+ BACK_ITEM(MSG_CONFIGURATION);
+
+ #if ENABLED(LCD_PROGRESS_BAR_TEST)
+ SUBMENU(MSG_PROGRESS_BAR_TEST, _progress_bar_test);
+ #endif
+
+ END_MENU();
+ }
+
+#endif
+
+#if HAS_MULTI_EXTRUDER
+
+ #include "../../module/tool_change.h"
+
+ void menu_tool_change() {
+ START_MENU();
+ BACK_ITEM(MSG_CONFIGURATION);
+ #if ENABLED(TOOLCHANGE_FILAMENT_SWAP)
+ static constexpr float max_extrude = TERN(PREVENT_LENGTHY_EXTRUDE, EXTRUDE_MAXLENGTH, 500);
+ #if ENABLED(TOOLCHANGE_PARK)
+ EDIT_ITEM(bool, MSG_FILAMENT_PARK_ENABLED, &toolchange_settings.enable_park);
+ #endif
+ EDIT_ITEM(float3, MSG_FILAMENT_SWAP_LENGTH, &toolchange_settings.swap_length, 0, max_extrude);
+ EDIT_ITEM(float41sign, MSG_FILAMENT_SWAP_EXTRA, &toolchange_settings.extra_resume, -10, 10);
+ EDIT_ITEM_FAST(int4, MSG_SINGLENOZZLE_RETRACT_SPEED, &toolchange_settings.retract_speed, 10, 5400);
+ EDIT_ITEM_FAST(int4, MSG_SINGLENOZZLE_UNRETRACT_SPEED, &toolchange_settings.unretract_speed, 10, 5400);
+ EDIT_ITEM(float3, MSG_FILAMENT_PURGE_LENGTH, &toolchange_settings.extra_prime, 0, max_extrude);
+ EDIT_ITEM_FAST(int4, MSG_SINGLENOZZLE_PRIME_SPEED, &toolchange_settings.prime_speed, 10, 5400);
+ EDIT_ITEM_FAST(int4, MSG_SINGLENOZZLE_FAN_SPEED, &toolchange_settings.fan_speed, 0, 255);
+ EDIT_ITEM_FAST(int4, MSG_SINGLENOZZLE_FAN_TIME, &toolchange_settings.fan_time, 1, 30);
+ #endif
+ EDIT_ITEM(float3, MSG_TOOL_CHANGE_ZLIFT, &toolchange_settings.z_raise, 0, 10);
+ END_MENU();
+ }
+
+ #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE)
+
+ #include "../../module/motion.h" // for active_extruder
+
+ void menu_toolchange_migration() {
+ PGM_P const msg_migrate = GET_TEXT(MSG_TOOL_MIGRATION_SWAP);
+
+ START_MENU();
+ BACK_ITEM(MSG_CONFIGURATION);
+
+ // Auto mode ON/OFF
+ EDIT_ITEM(bool, MSG_TOOL_MIGRATION_AUTO, &migration.automode);
+ EDIT_ITEM(uint8, MSG_TOOL_MIGRATION_END, &migration.last, 0, EXTRUDERS - 1);
+
+ // Migrate to a chosen extruder
+ LOOP_L_N(s, EXTRUDERS) {
+ if (s != active_extruder) {
+ ACTION_ITEM_N_P(s, msg_migrate, []{
+ char cmd[12];
+ sprintf_P(cmd, PSTR("M217 T%i"), int(MenuItemBase::itemIndex));
+ queue.inject(cmd);
+ });
+ }
+ }
+ END_MENU();
+ }
+ #endif
+
+#endif
+
+#if HAS_HOTEND_OFFSET
+ #include "../../module/motion.h"
+ #include "../../gcode/queue.h"
+
+ void menu_tool_offsets() {
+
+ auto _recalc_offsets = []{
+ if (active_extruder && all_axes_trusted()) { // For the 2nd extruder re-home so the next tool-change gets the new offsets.
+ queue.inject_P(G28_STR); // In future, we can babystep the 2nd extruder (if active), making homing unnecessary.
+ active_extruder = 0;
+ }
+ };
+
+ START_MENU();
+ BACK_ITEM(MSG_CONFIGURATION);
+ #if ENABLED(DUAL_X_CARRIAGE)
+ EDIT_ITEM_FAST(float42_52, MSG_HOTEND_OFFSET_X, &hotend_offset[1].x, float(X2_HOME_POS - 25), float(X2_HOME_POS + 25), _recalc_offsets);
+ #else
+ EDIT_ITEM_FAST(float42_52, MSG_HOTEND_OFFSET_X, &hotend_offset[1].x, -99.0, 99.0, _recalc_offsets);
+ #endif
+ EDIT_ITEM_FAST(float42_52, MSG_HOTEND_OFFSET_Y, &hotend_offset[1].y, -99.0, 99.0, _recalc_offsets);
+ EDIT_ITEM_FAST(float42_52, MSG_HOTEND_OFFSET_Z, &hotend_offset[1].z, Z_PROBE_LOW_POINT, 10.0, _recalc_offsets);
+ #if ENABLED(EEPROM_SETTINGS)
+ ACTION_ITEM(MSG_STORE_EEPROM, ui.store_settings);
+ #endif
+ END_MENU();
+ }
+#endif
+
+#if ENABLED(DUAL_X_CARRIAGE)
+
+ void menu_idex() {
+ const bool need_g28 = axes_should_home(_BV(Y_AXIS)|_BV(Z_AXIS));
+
+ START_MENU();
+ BACK_ITEM(MSG_CONFIGURATION);
+
+ GCODES_ITEM(MSG_IDEX_MODE_AUTOPARK, PSTR("M605S1\nG28X\nG1X0"));
+ GCODES_ITEM(MSG_IDEX_MODE_DUPLICATE, need_g28
+ ? PSTR("M605S1\nT0\nG28\nM605S2\nG28X\nG1X0") // If Y or Z is not homed, do a full G28 first
+ : PSTR("M605S1\nT0\nM605S2\nG28X\nG1X0")
+ );
+ GCODES_ITEM(MSG_IDEX_MODE_MIRRORED_COPY, need_g28
+ ? PSTR("M605S1\nT0\nG28\nM605S2\nG28X\nG1X0\nM605S3") // If Y or Z is not homed, do a full G28 first
+ : PSTR("M605S1\nT0\nM605S2\nG28 X\nG1X0\nM605S3")
+ );
+ GCODES_ITEM(MSG_IDEX_MODE_FULL_CTRL, PSTR("M605S0\nG28X"));
+
+ EDIT_ITEM(float42_52, MSG_IDEX_DUPE_GAP, &duplicate_extruder_x_offset, (X2_MIN_POS) - (X1_MIN_POS), (X_BED_SIZE) - 20);
+
+ END_MENU();
+ }
+
+#endif
+
+#if ENABLED(BLTOUCH)
+
+ #if ENABLED(BLTOUCH_LCD_VOLTAGE_MENU)
+ void bltouch_report() {
+ SERIAL_ECHOLNPAIR("EEPROM Last BLTouch Mode - ", (int)bltouch.last_written_mode);
+ SERIAL_ECHOLNPGM("Configuration BLTouch Mode - " TERN(BLTOUCH_SET_5V_MODE, "5V", "OD"));
+ char mess[21];
+ strcpy_P(mess, PSTR("BLTouch Mode - "));
+ strcpy_P(&mess[15], bltouch.last_written_mode ? PSTR("5V") : PSTR("OD"));
+ ui.set_status(mess);
+ ui.return_to_status();
+ }
+ #endif
+
+ void menu_bltouch() {
+ START_MENU();
+ BACK_ITEM(MSG_CONFIGURATION);
+ ACTION_ITEM(MSG_BLTOUCH_RESET, bltouch._reset);
+ ACTION_ITEM(MSG_BLTOUCH_SELFTEST, bltouch._selftest);
+ ACTION_ITEM(MSG_BLTOUCH_DEPLOY, bltouch._deploy);
+ ACTION_ITEM(MSG_BLTOUCH_STOW, bltouch._stow);
+ ACTION_ITEM(MSG_BLTOUCH_SW_MODE, bltouch._set_SW_mode);
+ #if ENABLED(BLTOUCH_LCD_VOLTAGE_MENU)
+ CONFIRM_ITEM(MSG_BLTOUCH_5V_MODE, MSG_BLTOUCH_5V_MODE, MSG_BUTTON_CANCEL, bltouch._set_5V_mode, nullptr, GET_TEXT(MSG_BLTOUCH_MODE_CHANGE));
+ CONFIRM_ITEM(MSG_BLTOUCH_OD_MODE, MSG_BLTOUCH_OD_MODE, MSG_BUTTON_CANCEL, bltouch._set_OD_mode, nullptr, GET_TEXT(MSG_BLTOUCH_MODE_CHANGE));
+ ACTION_ITEM(MSG_BLTOUCH_MODE_STORE, bltouch._mode_store);
+ CONFIRM_ITEM(MSG_BLTOUCH_MODE_STORE_5V, MSG_BLTOUCH_MODE_STORE_5V, MSG_BUTTON_CANCEL, bltouch.mode_conv_5V, nullptr, GET_TEXT(MSG_BLTOUCH_MODE_CHANGE));
+ CONFIRM_ITEM(MSG_BLTOUCH_MODE_STORE_OD, MSG_BLTOUCH_MODE_STORE_OD, MSG_BUTTON_CANCEL, bltouch.mode_conv_OD, nullptr, GET_TEXT(MSG_BLTOUCH_MODE_CHANGE));
+ ACTION_ITEM(MSG_BLTOUCH_MODE_ECHO, bltouch_report);
+ #endif
+ END_MENU();
+ }
+
+#endif
+
+#if ENABLED(TOUCH_MI_PROBE)
+
+ void menu_touchmi() {
+ ui.defer_status_screen();
+ START_MENU();
+ BACK_ITEM(MSG_CONFIGURATION);
+ GCODES_ITEM(MSG_TOUCHMI_INIT, PSTR("M851 Z0\nG28\nG1 F200 Z0"));
+ SUBMENU(MSG_ZPROBE_ZOFFSET, lcd_babystep_zoffset);
+ GCODES_ITEM(MSG_TOUCHMI_SAVE, PSTR("M500\nG1 F200 Z10"));
+ GCODES_ITEM(MSG_TOUCHMI_ZTEST, PSTR("G28\nG1 F200 Z0"));
+ END_MENU();
+ }
+
+#endif
+
+#if ENABLED(CONTROLLER_FAN_MENU)
+
+ #include "../../feature/controllerfan.h"
+
+ void menu_controller_fan() {
+ START_MENU();
+ BACK_ITEM(MSG_CONFIGURATION);
+ EDIT_ITEM_FAST(percent, MSG_CONTROLLER_FAN_IDLE_SPEED, &controllerFan.settings.idle_speed, _MAX(1, CONTROLLERFAN_SPEED_MIN) - 1, 255);
+ EDIT_ITEM(bool, MSG_CONTROLLER_FAN_AUTO_ON, &controllerFan.settings.auto_mode);
+ if (controllerFan.settings.auto_mode) {
+ EDIT_ITEM_FAST(percent, MSG_CONTROLLER_FAN_SPEED, &controllerFan.settings.active_speed, _MAX(1, CONTROLLERFAN_SPEED_MIN) - 1, 255);
+ EDIT_ITEM(uint16_4, MSG_CONTROLLER_FAN_DURATION, &controllerFan.settings.duration, 0, 4800);
+ }
+ END_MENU();
+ }
+
+#endif
+
+#if ENABLED(FWRETRACT)
+
+ #include "../../feature/fwretract.h"
+
+ void menu_config_retract() {
+ START_MENU();
+ BACK_ITEM(MSG_CONFIGURATION);
+ #if ENABLED(FWRETRACT_AUTORETRACT)
+ EDIT_ITEM(bool, MSG_AUTORETRACT, &fwretract.autoretract_enabled, fwretract.refresh_autoretract);
+ #endif
+ EDIT_ITEM(float52sign, MSG_CONTROL_RETRACT, &fwretract.settings.retract_length, 0, 100);
+ #if HAS_MULTI_EXTRUDER
+ EDIT_ITEM(float52sign, MSG_CONTROL_RETRACT_SWAP, &fwretract.settings.swap_retract_length, 0, 100);
+ #endif
+ EDIT_ITEM(float3, MSG_CONTROL_RETRACTF, &fwretract.settings.retract_feedrate_mm_s, 1, 999);
+ EDIT_ITEM(float52sign, MSG_CONTROL_RETRACT_ZHOP, &fwretract.settings.retract_zraise, 0, 999);
+ EDIT_ITEM(float52sign, MSG_CONTROL_RETRACT_RECOVER, &fwretract.settings.retract_recover_extra, -100, 100);
+ #if HAS_MULTI_EXTRUDER
+ EDIT_ITEM(float52sign, MSG_CONTROL_RETRACT_RECOVER_SWAP, &fwretract.settings.swap_retract_recover_extra, -100, 100);
+ #endif
+ EDIT_ITEM(float3, MSG_CONTROL_RETRACT_RECOVERF, &fwretract.settings.retract_recover_feedrate_mm_s, 1, 999);
+ #if HAS_MULTI_EXTRUDER
+ EDIT_ITEM(float3, MSG_CONTROL_RETRACT_RECOVER_SWAPF, &fwretract.settings.swap_retract_recover_feedrate_mm_s, 1, 999);
+ #endif
+ END_MENU();
+ }
+
+#endif
+
+#if PREHEAT_COUNT && DISABLED(SLIM_LCD_MENUS)
+
+ void _menu_configuration_preheat_settings() {
+ #define _MINTEMP_ITEM(N) HEATER_##N##_MINTEMP,
+ #define _MAXTEMP_ITEM(N) HEATER_##N##_MAXTEMP,
+ #define MINTEMP_ALL _MIN(REPEAT(HOTENDS, _MINTEMP_ITEM) 999)
+ #define MAXTEMP_ALL _MAX(REPEAT(HOTENDS, _MAXTEMP_ITEM) 0)
+ const uint8_t m = MenuItemBase::itemIndex;
+ START_MENU();
+ STATIC_ITEM_P(ui.get_preheat_label(m), SS_DEFAULT|SS_INVERT);
+ BACK_ITEM(MSG_CONFIGURATION);
+ #if HAS_FAN
+ editable.uint8 = uint8_t(ui.material_preset[m].fan_speed);
+ EDIT_ITEM_N(percent, m, MSG_FAN_SPEED, &editable.uint8, 0, 255, []{ ui.material_preset[MenuItemBase::itemIndex].fan_speed = editable.uint8; });
+ #endif
+ #if HAS_TEMP_HOTEND
+ EDIT_ITEM(uint16_3, MSG_NOZZLE, &ui.material_preset[m].hotend_temp, MINTEMP_ALL, MAXTEMP_ALL - HOTEND_OVERSHOOT);
+ #endif
+ #if HAS_HEATED_BED
+ EDIT_ITEM(uint16_3, MSG_BED, &ui.material_preset[m].bed_temp, BED_MINTEMP, BED_MAX_TARGET);
+ #endif
+ #if ENABLED(EEPROM_SETTINGS)
+ ACTION_ITEM(MSG_STORE_EEPROM, ui.store_settings);
+ #endif
+ END_MENU();
+ }
+
+#endif
+
+void menu_configuration() {
+ const bool busy = printer_busy();
+
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ //
+ // Debug Menu when certain options are enabled
+ //
+ #if HAS_DEBUG_MENU
+ SUBMENU(MSG_DEBUG_MENU, menu_debug);
+ #endif
+
+ SUBMENU(MSG_ADVANCED_SETTINGS, menu_advanced_settings);
+
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ SUBMENU(MSG_ZPROBE_ZOFFSET, lcd_babystep_zoffset);
+ #elif HAS_BED_PROBE
+ EDIT_ITEM(LCD_Z_OFFSET_TYPE, MSG_ZPROBE_ZOFFSET, &probe.offset.z, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX);
+ #endif
+
+ //
+ // Set Fan Controller speed
+ //
+ #if ENABLED(CONTROLLER_FAN_MENU)
+ SUBMENU(MSG_CONTROLLER_FAN, menu_controller_fan);
+ #endif
+
+ if (!busy) {
+ #if EITHER(DELTA_CALIBRATION_MENU, DELTA_AUTO_CALIBRATION)
+ SUBMENU(MSG_DELTA_CALIBRATE, menu_delta_calibrate);
+ #endif
+
+ #if HAS_HOTEND_OFFSET
+ SUBMENU(MSG_OFFSETS_MENU, menu_tool_offsets);
+ #endif
+
+ #if ENABLED(DUAL_X_CARRIAGE)
+ SUBMENU(MSG_IDEX_MENU, menu_idex);
+ #endif
+
+ #if ENABLED(BLTOUCH)
+ SUBMENU(MSG_BLTOUCH, menu_bltouch);
+ #endif
+
+ #if ENABLED(TOUCH_MI_PROBE)
+ SUBMENU(MSG_TOUCHMI_PROBE, menu_touchmi);
+ #endif
+ }
+
+ //
+ // Set single nozzle filament retract and prime length
+ //
+ #if HAS_MULTI_EXTRUDER
+ SUBMENU(MSG_TOOL_CHANGE, menu_tool_change);
+ #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE)
+ SUBMENU(MSG_TOOL_MIGRATION, menu_toolchange_migration);
+ #endif
+ #endif
+
+ #if HAS_LCD_CONTRAST
+ EDIT_ITEM(int3, MSG_CONTRAST, &ui.contrast, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX, ui.refresh_contrast, true);
+ #endif
+ #if ENABLED(FWRETRACT)
+ SUBMENU(MSG_RETRACT, menu_config_retract);
+ #endif
+
+ #if HAS_FILAMENT_SENSOR
+ EDIT_ITEM(bool, MSG_RUNOUT_SENSOR, &runout.enabled, runout.reset);
+ #endif
+
+ #if ENABLED(POWER_LOSS_RECOVERY)
+ EDIT_ITEM(bool, MSG_OUTAGE_RECOVERY, &recovery.enabled, recovery.changed);
+ #endif
+
+ // Preheat configurations
+ #if PREHEAT_COUNT && DISABLED(SLIM_LCD_MENUS)
+ LOOP_L_N(m, PREHEAT_COUNT)
+ SUBMENU_N_S(m, ui.get_preheat_label(m), MSG_PREHEAT_M_SETTINGS, _menu_configuration_preheat_settings);
+ #endif
+
+ #if ENABLED(SOUND_MENU_ITEM)
+ EDIT_ITEM(bool, MSG_SOUND, &ui.buzzer_enabled, []{ ui.chirp(); });
+ #endif
+
+ #if ENABLED(EEPROM_SETTINGS)
+ ACTION_ITEM(MSG_STORE_EEPROM, ui.store_settings);
+ if (!busy) ACTION_ITEM(MSG_LOAD_EEPROM, ui.load_settings);
+ #endif
+
+ if (!busy) ACTION_ITEM(MSG_RESTORE_DEFAULTS, ui.reset_settings);
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU
diff --git a/Marlin/src/lcd/menu/menu_custom.cpp b/Marlin/src/lcd/menu/menu_custom.cpp
new file mode 100644
index 0000000..7c54ec6
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_custom.cpp
@@ -0,0 +1,129 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Custom User Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, CUSTOM_USER_MENUS)
+
+#include "menu_item.h"
+#include "../../gcode/queue.h"
+
+#ifdef USER_SCRIPT_DONE
+ #define _DONE_SCRIPT "\n" USER_SCRIPT_DONE
+#else
+ #define _DONE_SCRIPT ""
+#endif
+
+void _lcd_user_gcode(PGM_P const cmd) {
+ queue.inject_P(cmd);
+ TERN_(USER_SCRIPT_AUDIBLE_FEEDBACK, ui.completion_feedback());
+ TERN_(USER_SCRIPT_RETURN, ui.return_to_status());
+}
+
+void menu_user() {
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+ #define HAS_USER_ITEM(N) (defined(USER_DESC_##N) && defined(USER_GCODE_##N))
+ #define USER_ITEM(N) ACTION_ITEM_P(PSTR(USER_DESC_##N), []{ _lcd_user_gcode(PSTR(USER_GCODE_##N _DONE_SCRIPT)); });
+ #if HAS_USER_ITEM(1)
+ USER_ITEM(1);
+ #endif
+ #if HAS_USER_ITEM(2)
+ USER_ITEM(2);
+ #endif
+ #if HAS_USER_ITEM(3)
+ USER_ITEM(3);
+ #endif
+ #if HAS_USER_ITEM(4)
+ USER_ITEM(4);
+ #endif
+ #if HAS_USER_ITEM(5)
+ USER_ITEM(5);
+ #endif
+ #if HAS_USER_ITEM(6)
+ USER_ITEM(6);
+ #endif
+ #if HAS_USER_ITEM(7)
+ USER_ITEM(7);
+ #endif
+ #if HAS_USER_ITEM(8)
+ USER_ITEM(8);
+ #endif
+ #if HAS_USER_ITEM(9)
+ USER_ITEM(9);
+ #endif
+ #if HAS_USER_ITEM(10)
+ USER_ITEM(10);
+ #endif
+ #if HAS_USER_ITEM(11)
+ USER_ITEM(11);
+ #endif
+ #if HAS_USER_ITEM(12)
+ USER_ITEM(12);
+ #endif
+ #if HAS_USER_ITEM(13)
+ USER_ITEM(13);
+ #endif
+ #if HAS_USER_ITEM(14)
+ USER_ITEM(14);
+ #endif
+ #if HAS_USER_ITEM(15)
+ USER_ITEM(15);
+ #endif
+ #if HAS_USER_ITEM(16)
+ USER_ITEM(16);
+ #endif
+ #if HAS_USER_ITEM(17)
+ USER_ITEM(17);
+ #endif
+ #if HAS_USER_ITEM(18)
+ USER_ITEM(18);
+ #endif
+ #if HAS_USER_ITEM(19)
+ USER_ITEM(19);
+ #endif
+ #if HAS_USER_ITEM(20)
+ USER_ITEM(20);
+ #endif
+ #if HAS_USER_ITEM(21)
+ USER_ITEM(21);
+ #endif
+ #if HAS_USER_ITEM(22)
+ USER_ITEM(22);
+ #endif
+ #if HAS_USER_ITEM(23)
+ USER_ITEM(23);
+ #endif
+ #if HAS_USER_ITEM(24)
+ USER_ITEM(24);
+ #endif
+ #if HAS_USER_ITEM(25)
+ USER_ITEM(25);
+ #endif
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && CUSTOM_USER_MENUS
diff --git a/Marlin/src/lcd/menu/menu_delta_calibrate.cpp b/Marlin/src/lcd/menu/menu_delta_calibrate.cpp
new file mode 100644
index 0000000..a86ae74
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_delta_calibrate.cpp
@@ -0,0 +1,150 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Delta Calibrate Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_LCD_MENU && EITHER(DELTA_CALIBRATION_MENU, DELTA_AUTO_CALIBRATION)
+
+#include "menu_item.h"
+#include "../../module/delta.h"
+#include "../../module/motion.h"
+
+#if HAS_LEVELING
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+#if ENABLED(EXTENSIBLE_UI)
+ #include "../../lcd/extui/ui_api.h"
+#endif
+
+void _man_probe_pt(const xy_pos_t &xy) {
+ if (!ui.wait_for_move) {
+ ui.wait_for_move = true;
+ do_blocking_move_to_xy_z(xy, Z_CLEARANCE_BETWEEN_PROBES);
+ ui.wait_for_move = false;
+ ui.synchronize();
+ ui.manual_move.menu_scale = _MAX(PROBE_MANUALLY_STEP, MIN_STEPS_PER_SEGMENT / float(DEFAULT_XYZ_STEPS_PER_UNIT));
+ ui.goto_screen(lcd_move_z);
+ }
+}
+
+#if ENABLED(DELTA_AUTO_CALIBRATION)
+
+ #include "../../MarlinCore.h" // for wait_for_user_response()
+ #include "../../gcode/gcode.h"
+
+ #if ENABLED(HOST_PROMPT_SUPPORT)
+ #include "../../feature/host_actions.h" // for host_prompt_do
+ #endif
+
+ float lcd_probe_pt(const xy_pos_t &xy) {
+ _man_probe_pt(xy);
+ ui.defer_status_screen();
+ TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, PSTR("Delta Calibration in progress"), CONTINUE_STR));
+ TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(PSTR("Delta Calibration in progress")));
+ wait_for_user_response();
+ ui.goto_previous_screen_no_defer();
+ return current_position.z;
+ }
+
+#endif
+
+#if ENABLED(DELTA_CALIBRATION_MENU)
+
+ #include "../../gcode/queue.h"
+
+ void _lcd_calibrate_homing() {
+ _lcd_draw_homing();
+ if (all_axes_homed()) ui.goto_previous_screen();
+ }
+
+ void _lcd_delta_calibrate_home() {
+ queue.inject_P(G28_STR);
+ ui.goto_screen(_lcd_calibrate_homing);
+ }
+
+ void _goto_tower_a(const float &a) {
+ xy_pos_t tower_vec = { cos(RADIANS(a)), sin(RADIANS(a)) };
+ _man_probe_pt(tower_vec * delta_calibration_radius());
+ }
+ void _goto_tower_x() { _goto_tower_a(210); }
+ void _goto_tower_y() { _goto_tower_a(330); }
+ void _goto_tower_z() { _goto_tower_a( 90); }
+ void _goto_center() { xy_pos_t ctr{0}; _man_probe_pt(ctr); }
+
+#endif
+
+void lcd_delta_settings() {
+ auto _recalc_delta_settings = []{
+ TERN_(HAS_LEVELING, reset_bed_level()); // After changing kinematics bed-level data is no longer valid
+ recalc_delta_settings();
+ };
+ START_MENU();
+ BACK_ITEM(MSG_DELTA_CALIBRATE);
+ EDIT_ITEM(float52sign, MSG_DELTA_HEIGHT, &delta_height, delta_height - 10, delta_height + 10, _recalc_delta_settings);
+ #define EDIT_ENDSTOP_ADJ(LABEL,N) EDIT_ITEM_P(float43, PSTR(LABEL), &delta_endstop_adj.N, -5, 5, _recalc_delta_settings)
+ EDIT_ENDSTOP_ADJ("Ex", a);
+ EDIT_ENDSTOP_ADJ("Ey", b);
+ EDIT_ENDSTOP_ADJ("Ez", c);
+ EDIT_ITEM(float52sign, MSG_DELTA_RADIUS, &delta_radius, delta_radius - 5, delta_radius + 5, _recalc_delta_settings);
+ #define EDIT_ANGLE_TRIM(LABEL,N) EDIT_ITEM_P(float43, PSTR(LABEL), &delta_tower_angle_trim.N, -5, 5, _recalc_delta_settings)
+ EDIT_ANGLE_TRIM("Tx", a);
+ EDIT_ANGLE_TRIM("Ty", b);
+ EDIT_ANGLE_TRIM("Tz", c);
+ EDIT_ITEM(float52sign, MSG_DELTA_DIAG_ROD, &delta_diagonal_rod, delta_diagonal_rod - 5, delta_diagonal_rod + 5, _recalc_delta_settings);
+ END_MENU();
+}
+
+void menu_delta_calibrate() {
+ TERN_(DELTA_CALIBRATION_MENU, const bool all_homed = all_axes_homed()); // Acquire ahead of loop
+
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ #if ENABLED(DELTA_AUTO_CALIBRATION)
+ GCODES_ITEM(MSG_DELTA_AUTO_CALIBRATE, PSTR("G33"));
+ #if ENABLED(EEPROM_SETTINGS)
+ ACTION_ITEM(MSG_STORE_EEPROM, ui.store_settings);
+ ACTION_ITEM(MSG_LOAD_EEPROM, ui.load_settings);
+ #endif
+ #endif
+
+ SUBMENU(MSG_DELTA_SETTINGS, lcd_delta_settings);
+
+ #if ENABLED(DELTA_CALIBRATION_MENU)
+ SUBMENU(MSG_AUTO_HOME, _lcd_delta_calibrate_home);
+ if (all_homed) {
+ SUBMENU(MSG_DELTA_CALIBRATE_X, _goto_tower_x);
+ SUBMENU(MSG_DELTA_CALIBRATE_Y, _goto_tower_y);
+ SUBMENU(MSG_DELTA_CALIBRATE_Z, _goto_tower_z);
+ SUBMENU(MSG_DELTA_CALIBRATE_CENTER, _goto_center);
+ }
+ #endif
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && (DELTA_CALIBRATION_MENU || DELTA_AUTO_CALIBRATION)
diff --git a/Marlin/src/lcd/menu/menu_filament.cpp b/Marlin/src/lcd/menu/menu_filament.cpp
new file mode 100644
index 0000000..19601d6
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_filament.cpp
@@ -0,0 +1,336 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Filament Change Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, ADVANCED_PAUSE_FEATURE)
+
+#include "menu_item.h"
+#include "../../module/temperature.h"
+#include "../../feature/pause.h"
+#include "../../gcode/queue.h"
+#if HAS_FILAMENT_SENSOR
+ #include "../../feature/runout.h"
+#endif
+
+//
+// Change Filament > Change/Unload/Load Filament
+//
+static PauseMode _change_filament_mode; // = PAUSE_MODE_PAUSE_PRINT
+static int8_t _change_filament_extruder; // = 0
+
+inline PGM_P _change_filament_command() {
+ switch (_change_filament_mode) {
+ case PAUSE_MODE_LOAD_FILAMENT: return PSTR("M701 T%d");
+ case PAUSE_MODE_UNLOAD_FILAMENT: return _change_filament_extruder >= 0
+ ? PSTR("M702 T%d") : PSTR("M702 ;%d");
+ case PAUSE_MODE_CHANGE_FILAMENT:
+ case PAUSE_MODE_PAUSE_PRINT:
+ default: break;
+ }
+ return PSTR("M600 B0 T%d");
+}
+
+// Initiate Filament Load/Unload/Change at the specified temperature
+static void _change_filament_with_temp(const uint16_t celsius) {
+ char cmd[11];
+ sprintf_P(cmd, _change_filament_command(), _change_filament_extruder);
+ thermalManager.setTargetHotend(celsius, _change_filament_extruder);
+ queue.inject(cmd);
+}
+
+static void _change_filament_with_preset() {
+ _change_filament_with_temp(ui.material_preset[MenuItemBase::itemIndex].hotend_temp);
+}
+
+static void _change_filament_with_custom() {
+ _change_filament_with_temp(thermalManager.temp_hotend[MenuItemBase::itemIndex].target);
+}
+
+//
+// Menu to choose the temperature and start Filament Change
+//
+
+inline PGM_P change_filament_header(const PauseMode mode) {
+ switch (mode) {
+ case PAUSE_MODE_LOAD_FILAMENT: return GET_TEXT(MSG_FILAMENTLOAD);
+ case PAUSE_MODE_UNLOAD_FILAMENT: return GET_TEXT(MSG_FILAMENTUNLOAD);
+ default: break;
+ }
+ return GET_TEXT(MSG_FILAMENTCHANGE);
+}
+
+void _menu_temp_filament_op(const PauseMode mode, const int8_t extruder) {
+ _change_filament_mode = mode;
+ _change_filament_extruder = extruder;
+ const int8_t old_index = MenuItemBase::itemIndex;
+ START_MENU();
+ if (LCD_HEIGHT >= 4) STATIC_ITEM_P(change_filament_header(mode), SS_DEFAULT|SS_INVERT);
+ BACK_ITEM(MSG_BACK);
+ #if PREHEAT_COUNT
+ LOOP_L_N(m, PREHEAT_COUNT)
+ ACTION_ITEM_N_S(m, ui.get_preheat_label(m), MSG_PREHEAT_M, _change_filament_with_preset);
+ #endif
+ EDIT_ITEM_FAST_N(int3, extruder, MSG_PREHEAT_CUSTOM, &thermalManager.temp_hotend[extruder].target,
+ EXTRUDE_MINTEMP, thermalManager.heater_maxtemp[extruder] - HOTEND_OVERSHOOT,
+ _change_filament_with_custom
+ );
+ END_MENU();
+ MenuItemBase::itemIndex = old_index;
+}
+
+/**
+ * "Change Filament" submenu
+ */
+#if E_STEPPERS > 1 || ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+
+ bool printingIsPaused();
+
+ void menu_change_filament() {
+ // Say "filament change" when no print is active
+ editable.int8 = printingIsPaused() ? PAUSE_MODE_PAUSE_PRINT : PAUSE_MODE_CHANGE_FILAMENT;
+
+ #if E_STEPPERS > 1 && ENABLED(FILAMENT_UNLOAD_ALL_EXTRUDERS)
+ bool too_cold = false;
+ for (uint8_t s = 0; !too_cold && s < E_STEPPERS; s++)
+ too_cold = thermalManager.targetTooColdToExtrude(s);
+ #endif
+
+ #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ const bool is_busy = printer_busy();
+ #endif
+
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ // Change filament
+ #if E_STEPPERS == 1
+ PGM_P const msg = GET_TEXT(MSG_FILAMENTCHANGE);
+ if (thermalManager.targetTooColdToExtrude(active_extruder))
+ SUBMENU_P(msg, []{ _menu_temp_filament_op(PAUSE_MODE_CHANGE_FILAMENT, 0); });
+ else
+ GCODES_ITEM_P(msg, PSTR("M600 B0"));
+ #else
+ PGM_P const msg = GET_TEXT(MSG_FILAMENTCHANGE_E);
+ LOOP_L_N(s, E_STEPPERS) {
+ if (thermalManager.targetTooColdToExtrude(s))
+ SUBMENU_N_P(s, msg, []{ _menu_temp_filament_op(PAUSE_MODE_CHANGE_FILAMENT, MenuItemBase::itemIndex); });
+ else {
+ ACTION_ITEM_N_P(s, msg, []{
+ PGM_P const cmdpstr = PSTR("M600 B0 T%i");
+ char cmd[strlen_P(cmdpstr) + 3 + 1];
+ sprintf_P(cmd, cmdpstr, int(MenuItemBase::itemIndex));
+ queue.inject(cmd);
+ });
+ }
+ }
+ #endif
+
+ #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ if (!is_busy) {
+ // Load filament
+ #if E_STEPPERS == 1
+ PGM_P const msg_load = GET_TEXT(MSG_FILAMENTLOAD);
+ if (thermalManager.targetTooColdToExtrude(active_extruder))
+ SUBMENU_P(msg_load, []{ _menu_temp_filament_op(PAUSE_MODE_LOAD_FILAMENT, 0); });
+ else
+ GCODES_ITEM_P(msg_load, PSTR("M701"));
+ #else
+ PGM_P const msg_load = GET_TEXT(MSG_FILAMENTLOAD_E);
+ LOOP_L_N(s, E_STEPPERS) {
+ if (thermalManager.targetTooColdToExtrude(s))
+ SUBMENU_N_P(s, msg_load, []{ _menu_temp_filament_op(PAUSE_MODE_LOAD_FILAMENT, MenuItemBase::itemIndex); });
+ else {
+ ACTION_ITEM_N_P(s, msg_load, []{
+ char cmd[12];
+ sprintf_P(cmd, PSTR("M701 T%i"), int(MenuItemBase::itemIndex));
+ queue.inject(cmd);
+ });
+ }
+ }
+ #endif
+
+ // Unload filament
+ #if E_STEPPERS == 1
+ PGM_P const msg_unload = GET_TEXT(MSG_FILAMENTUNLOAD);
+ if (thermalManager.targetTooColdToExtrude(active_extruder))
+ SUBMENU_P(msg_unload, []{ _menu_temp_filament_op(PAUSE_MODE_UNLOAD_FILAMENT, 0); });
+ else
+ GCODES_ITEM_P(msg_unload, PSTR("M702"));
+ #else
+ #if ENABLED(FILAMENT_UNLOAD_ALL_EXTRUDERS)
+ if (too_cold)
+ SUBMENU(MSG_FILAMENTUNLOAD_ALL, []{ _menu_temp_filament_op(PAUSE_MODE_UNLOAD_FILAMENT, -1); });
+ else
+ GCODES_ITEM(MSG_FILAMENTUNLOAD_ALL, PSTR("M702"));
+ #endif
+ PGM_P const msg_unload = GET_TEXT(MSG_FILAMENTUNLOAD_E);
+ LOOP_L_N(s, E_STEPPERS) {
+ if (thermalManager.targetTooColdToExtrude(s))
+ SUBMENU_N_P(s, msg_unload, []{ _menu_temp_filament_op(PAUSE_MODE_UNLOAD_FILAMENT, MenuItemBase::itemIndex); });
+ else {
+ ACTION_ITEM_N_P(s, msg_unload, []{
+ char cmd[12];
+ sprintf_P(cmd, PSTR("M702 T%i"), int(MenuItemBase::itemIndex));
+ queue.inject(cmd);
+ });
+ }
+ }
+ #endif
+ } // !printer_busy
+ #endif
+
+ END_MENU();
+ }
+#endif
+
+static uint8_t hotend_status_extruder = 0;
+
+static PGM_P pause_header() {
+ switch (pause_mode) {
+ case PAUSE_MODE_CHANGE_FILAMENT: return GET_TEXT(MSG_FILAMENT_CHANGE_HEADER);
+ case PAUSE_MODE_LOAD_FILAMENT: return GET_TEXT(MSG_FILAMENT_CHANGE_HEADER_LOAD);
+ case PAUSE_MODE_UNLOAD_FILAMENT: return GET_TEXT(MSG_FILAMENT_CHANGE_HEADER_UNLOAD);
+ default: break;
+ }
+ return GET_TEXT(MSG_FILAMENT_CHANGE_HEADER_PAUSE);
+}
+
+// Portions from STATIC_ITEM...
+#define HOTEND_STATUS_ITEM() do { \
+ if (_menuLineNr == _thisItemNr) { \
+ if (ui.should_draw()) { \
+ IF_DISABLED(HAS_GRAPHICAL_TFT, MenuItem_static::draw(_lcdLineNr, GET_TEXT(MSG_FILAMENT_CHANGE_NOZZLE), SS_INVERT)); \
+ ui.draw_hotend_status(_lcdLineNr, hotend_status_extruder); \
+ } \
+ if (_skipStatic && encoderLine <= _thisItemNr) { \
+ ui.encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \
+ ++encoderLine; \
+ } \
+ ui.refresh(LCDVIEW_CALL_REDRAW_NEXT); \
+ } \
+ ++_thisItemNr; \
+}while(0)
+
+void menu_pause_option() {
+ START_MENU();
+ #if LCD_HEIGHT > 2
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_OPTION_HEADER);
+ #endif
+ ACTION_ITEM(MSG_FILAMENT_CHANGE_OPTION_PURGE, []{ pause_menu_response = PAUSE_RESPONSE_EXTRUDE_MORE; });
+
+ #if HAS_FILAMENT_SENSOR
+ const bool still_out = runout.filament_ran_out;
+ if (still_out)
+ EDIT_ITEM(bool, MSG_RUNOUT_SENSOR, &runout.enabled, runout.reset);
+ #else
+ constexpr bool still_out = false;
+ #endif
+
+ if (!still_out)
+ ACTION_ITEM(MSG_FILAMENT_CHANGE_OPTION_RESUME, []{ pause_menu_response = PAUSE_RESPONSE_RESUME_PRINT; });
+
+ END_MENU();
+}
+
+//
+// ADVANCED_PAUSE_FEATURE message screens
+//
+// Warning: msg must have three null bytes to delimit lines!
+//
+void _lcd_pause_message(PGM_P const msg) {
+ PGM_P const msg1 = msg;
+ PGM_P const msg2 = msg1 + strlen_P(msg1) + 1;
+ PGM_P const msg3 = msg2 + strlen_P(msg2) + 1;
+ const bool has2 = msg2[0], has3 = msg3[0],
+ skip1 = !has2 && (LCD_HEIGHT) >= 5;
+
+ START_SCREEN();
+ STATIC_ITEM_P(pause_header(), SS_DEFAULT|SS_INVERT); // 1: Header
+ if (skip1) SKIP_ITEM(); // Move a single-line message down
+ STATIC_ITEM_P(msg1); // 2: Message Line 1
+ if (has2) STATIC_ITEM_P(msg2); // 3: Message Line 2
+ if (has3 && (LCD_HEIGHT) >= 5) STATIC_ITEM_P(msg3); // 4: Message Line 3 (if LCD has 5 lines)
+ if (skip1 + 1 + has2 + has3 < (LCD_HEIGHT) - 2) SKIP_ITEM(); // Push Hotend Status down, if needed
+ HOTEND_STATUS_ITEM(); // 5: Hotend Status
+ END_SCREEN();
+}
+
+void lcd_pause_parking_message() { _lcd_pause_message(GET_TEXT(MSG_PAUSE_PRINT_PARKING)); }
+void lcd_pause_changing_message() { _lcd_pause_message(GET_TEXT(MSG_FILAMENT_CHANGE_INIT)); }
+void lcd_pause_unload_message() { _lcd_pause_message(GET_TEXT(MSG_FILAMENT_CHANGE_UNLOAD)); }
+void lcd_pause_heating_message() { _lcd_pause_message(GET_TEXT(MSG_FILAMENT_CHANGE_HEATING)); }
+void lcd_pause_heat_message() { _lcd_pause_message(GET_TEXT(MSG_FILAMENT_CHANGE_HEAT)); }
+void lcd_pause_insert_message() { _lcd_pause_message(GET_TEXT(MSG_FILAMENT_CHANGE_INSERT)); }
+void lcd_pause_load_message() { _lcd_pause_message(GET_TEXT(MSG_FILAMENT_CHANGE_LOAD)); }
+void lcd_pause_waiting_message() { _lcd_pause_message(GET_TEXT(MSG_ADVANCED_PAUSE_WAITING)); }
+void lcd_pause_resume_message() { _lcd_pause_message(GET_TEXT(MSG_FILAMENT_CHANGE_RESUME)); }
+
+void lcd_pause_purge_message() {
+ #if ENABLED(ADVANCED_PAUSE_CONTINUOUS_PURGE)
+ _lcd_pause_message(GET_TEXT(MSG_FILAMENT_CHANGE_CONT_PURGE));
+ #else
+ _lcd_pause_message(GET_TEXT(MSG_FILAMENT_CHANGE_PURGE));
+ #endif
+}
+
+FORCE_INLINE screenFunc_t ap_message_screen(const PauseMessage message) {
+ switch (message) {
+ case PAUSE_MESSAGE_PARKING: return lcd_pause_parking_message;
+ case PAUSE_MESSAGE_CHANGING: return lcd_pause_changing_message;
+ case PAUSE_MESSAGE_UNLOAD: return lcd_pause_unload_message;
+ case PAUSE_MESSAGE_WAITING: return lcd_pause_waiting_message;
+ case PAUSE_MESSAGE_INSERT: return lcd_pause_insert_message;
+ case PAUSE_MESSAGE_LOAD: return lcd_pause_load_message;
+ case PAUSE_MESSAGE_PURGE: return lcd_pause_purge_message;
+ case PAUSE_MESSAGE_RESUME: return lcd_pause_resume_message;
+ case PAUSE_MESSAGE_HEAT: return lcd_pause_heat_message;
+ case PAUSE_MESSAGE_HEATING: return lcd_pause_heating_message;
+ case PAUSE_MESSAGE_OPTION: pause_menu_response = PAUSE_RESPONSE_WAIT_FOR;
+ return menu_pause_option;
+ case PAUSE_MESSAGE_STATUS:
+ default: break;
+ }
+ return nullptr;
+}
+
+void MarlinUI::pause_show_message(
+ const PauseMessage message,
+ const PauseMode mode/*=PAUSE_MODE_SAME*/,
+ const uint8_t extruder/*=active_extruder*/
+) {
+ if (mode != PAUSE_MODE_SAME) pause_mode = mode;
+ hotend_status_extruder = extruder;
+ const screenFunc_t next_screen = ap_message_screen(message);
+ if (next_screen) {
+ ui.defer_status_screen();
+ ui.goto_screen(next_screen);
+ }
+ else
+ ui.return_to_status();
+}
+
+#endif // HAS_LCD_MENU && ADVANCED_PAUSE_FEATURE
diff --git a/Marlin/src/lcd/menu/menu_game.cpp b/Marlin/src/lcd/menu/menu_game.cpp
new file mode 100644
index 0000000..fa56d7e
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_game.cpp
@@ -0,0 +1,48 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_GAME_MENU
+
+#include "menu_item.h"
+#include "game/game.h"
+
+void menu_game() {
+ START_MENU();
+ BACK_ITEM(TERN(LCD_INFO_MENU, MSG_INFO_MENU, MSG_MAIN));
+ #if ENABLED(MARLIN_BRICKOUT)
+ SUBMENU(MSG_BRICKOUT, brickout.enter_game);
+ #endif
+ #if ENABLED(MARLIN_INVADERS)
+ SUBMENU(MSG_INVADERS, invaders.enter_game);
+ #endif
+ #if ENABLED(MARLIN_SNAKE)
+ SUBMENU(MSG_SNAKE, snake.enter_game);
+ #endif
+ #if ENABLED(MARLIN_MAZE)
+ SUBMENU(MSG_MAZE, maze.enter_game);
+ #endif
+ END_MENU();
+}
+
+#endif // HAS_GAME_MENU
diff --git a/Marlin/src/lcd/menu/menu_info.cpp b/Marlin/src/lcd/menu/menu_info.cpp
new file mode 100644
index 0000000..a4cbc31
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_info.cpp
@@ -0,0 +1,306 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Info Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, LCD_INFO_MENU)
+
+#include "menu_item.h"
+
+#if HAS_GAMES
+ #include "game/game.h"
+#endif
+
+#define VALUE_ITEM(MSG, VALUE, STYL) do{ char msg[21]; strcpy_P(msg, PSTR(": ")); strcpy(msg + 2, VALUE); STATIC_ITEM(MSG, STYL, msg); }while(0)
+#define VALUE_ITEM_P(MSG, PVALUE, STYL) do{ char msg[21]; strcpy_P(msg, PSTR(": ")); strcpy_P(msg + 2, PSTR(PVALUE)); STATIC_ITEM(MSG, STYL, msg); }while(0)
+
+#if ENABLED(PRINTCOUNTER)
+
+ #include "../../module/printcounter.h"
+
+ //
+ // About Printer > Printer Stats
+ //
+ void menu_info_stats() {
+ if (ui.use_click()) return ui.go_back();
+
+ printStatistics stats = print_job_timer.getStats();
+
+ char buffer[21];
+
+ START_SCREEN(); // 12345678901234567890
+ VALUE_ITEM(MSG_INFO_PRINT_COUNT, i16tostr3left(stats.totalPrints), SS_LEFT); // Print Count: 999
+ VALUE_ITEM(MSG_INFO_COMPLETED_PRINTS, i16tostr3left(stats.finishedPrints), SS_LEFT); // Completed : 666
+
+ STATIC_ITEM(MSG_INFO_PRINT_TIME, SS_LEFT); // Total print Time:
+ STATIC_ITEM_P(PSTR("> "), SS_LEFT, duration_t(stats.printTime).toString(buffer)); // > 99y 364d 23h 59m 59s
+
+ STATIC_ITEM(MSG_INFO_PRINT_LONGEST, SS_LEFT); // Longest job time:
+ STATIC_ITEM_P(PSTR("> "), SS_LEFT, duration_t(stats.longestPrint).toString(buffer)); // > 99y 364d 23h 59m 59s
+
+ STATIC_ITEM(MSG_INFO_PRINT_FILAMENT, SS_LEFT); // Extruded total:
+ sprintf_P(buffer, PSTR("%ld.%im")
+ , long(stats.filamentUsed / 1000)
+ , int16_t(stats.filamentUsed / 100) % 10
+ );
+ STATIC_ITEM_P(PSTR("> "), SS_LEFT, buffer); // > 125m
+
+ #if SERVICE_INTERVAL_1 > 0 || SERVICE_INTERVAL_2 > 0 || SERVICE_INTERVAL_3 > 0
+ strcpy_P(buffer, GET_TEXT(MSG_SERVICE_IN));
+ #endif
+
+ #if SERVICE_INTERVAL_1 > 0
+ STATIC_ITEM_P(PSTR(SERVICE_NAME_1 " "), SS_LEFT, buffer); // Service X in:
+ STATIC_ITEM_P(PSTR("> "), SS_LEFT, duration_t(stats.nextService1).toString(buffer)); // > 7d 12h 11m 10s
+ #endif
+
+ #if SERVICE_INTERVAL_2 > 0
+ STATIC_ITEM_P(PSTR(SERVICE_NAME_2 " "), SS_LEFT, buffer);
+ STATIC_ITEM_P(PSTR("> "), SS_LEFT, duration_t(stats.nextService2).toString(buffer));
+ #endif
+
+ #if SERVICE_INTERVAL_3 > 0
+ STATIC_ITEM_P(PSTR(SERVICE_NAME_3 " "), SS_LEFT, buffer);
+ STATIC_ITEM_P(PSTR("> "), SS_LEFT, duration_t(stats.nextService3).toString(buffer));
+ #endif
+
+ END_SCREEN();
+ }
+
+#endif
+
+//
+// About Printer > Thermistors
+//
+void menu_info_thermistors() {
+ if (ui.use_click()) return ui.go_back();
+
+ START_SCREEN();
+
+ #if EXTRUDERS
+ #define THERMISTOR_ID TEMP_SENSOR_0
+ #include "../thermistornames.h"
+ STATIC_ITEM_P(PSTR(LCD_STR_E0 ": " THERMISTOR_NAME), SS_INVERT);
+ PSTRING_ITEM(MSG_INFO_MIN_TEMP, STRINGIFY(HEATER_0_MINTEMP), SS_LEFT);
+ PSTRING_ITEM(MSG_INFO_MAX_TEMP, STRINGIFY(HEATER_0_MAXTEMP), SS_LEFT);
+ #endif
+
+ #if TEMP_SENSOR_1 != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_1
+ #include "../thermistornames.h"
+ STATIC_ITEM_P(PSTR(LCD_STR_E1 ": " THERMISTOR_NAME), SS_INVERT);
+ PSTRING_ITEM(MSG_INFO_MIN_TEMP, STRINGIFY(HEATER_1_MINTEMP), SS_LEFT);
+ PSTRING_ITEM(MSG_INFO_MAX_TEMP, STRINGIFY(HEATER_1_MAXTEMP), SS_LEFT);
+ #endif
+
+ #if TEMP_SENSOR_2 != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_2
+ #include "../thermistornames.h"
+ STATIC_ITEM_P(PSTR(LCD_STR_E2 ": " THERMISTOR_NAME), SS_INVERT);
+ PSTRING_ITEM(MSG_INFO_MIN_TEMP, STRINGIFY(HEATER_2_MINTEMP), SS_LEFT);
+ PSTRING_ITEM(MSG_INFO_MAX_TEMP, STRINGIFY(HEATER_2_MAXTEMP), SS_LEFT);
+ #endif
+
+ #if TEMP_SENSOR_3 != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_3
+ #include "../thermistornames.h"
+ STATIC_ITEM_P(PSTR(LCD_STR_E3 ": " THERMISTOR_NAME), SS_INVERT);
+ PSTRING_ITEM(MSG_INFO_MIN_TEMP, STRINGIFY(HEATER_3_MINTEMP), SS_LEFT);
+ PSTRING_ITEM(MSG_INFO_MAX_TEMP, STRINGIFY(HEATER_3_MAXTEMP), SS_LEFT);
+ #endif
+
+ #if TEMP_SENSOR_4 != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_4
+ #include "../thermistornames.h"
+ STATIC_ITEM_P(PSTR(LCD_STR_E4 ": " THERMISTOR_NAME), SS_INVERT);
+ PSTRING_ITEM(MSG_INFO_MIN_TEMP, STRINGIFY(HEATER_4_MINTEMP), SS_LEFT);
+ PSTRING_ITEM(MSG_INFO_MAX_TEMP, STRINGIFY(HEATER_4_MAXTEMP), SS_LEFT);
+ #endif
+
+ #if TEMP_SENSOR_5 != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_5
+ #include "../thermistornames.h"
+ STATIC_ITEM_P(PSTR(LCD_STR_E5 ": " THERMISTOR_NAME), SS_INVERT);
+ PSTRING_ITEM(MSG_INFO_MIN_TEMP, STRINGIFY(HEATER_5_MINTEMP), SS_LEFT);
+ PSTRING_ITEM(MSG_INFO_MAX_TEMP, STRINGIFY(HEATER_5_MAXTEMP), SS_LEFT);
+ #endif
+
+ #if TEMP_SENSOR_6 != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_6
+ #include "../thermistornames.h"
+ STATIC_ITEM_P(PSTR(LCD_STR_E6 ": " THERMISTOR_NAME), SS_INVERT);
+ PSTRING_ITEM(MSG_INFO_MIN_TEMP, STRINGIFY(HEATER_6_MINTEMP), SS_LEFT);
+ PSTRING_ITEM(MSG_INFO_MAX_TEMP, STRINGIFY(HEATER_6_MAXTEMP), SS_LEFT);
+ #endif
+
+ #if TEMP_SENSOR_7 != 0
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_7
+ #include "../thermistornames.h"
+ STATIC_ITEM_P(PSTR(LCD_STR_E7 ": " THERMISTOR_NAME), SS_INVERT);
+ PSTRING_ITEM(MSG_INFO_MIN_TEMP, STRINGIFY(HEATER_7_MINTEMP), SS_LEFT);
+ PSTRING_ITEM(MSG_INFO_MAX_TEMP, STRINGIFY(HEATER_7_MAXTEMP), SS_LEFT);
+ #endif
+
+ #if EXTRUDERS
+ STATIC_ITEM(TERN(WATCH_HOTENDS, MSG_INFO_RUNAWAY_ON, MSG_INFO_RUNAWAY_OFF), SS_LEFT);
+ #endif
+
+ #if HAS_HEATED_BED
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_BED
+ #include "../thermistornames.h"
+ STATIC_ITEM_P(PSTR("BED: " THERMISTOR_NAME), SS_INVERT);
+ PSTRING_ITEM(MSG_INFO_MIN_TEMP, STRINGIFY(BED_MINTEMP), SS_LEFT);
+ PSTRING_ITEM(MSG_INFO_MAX_TEMP, STRINGIFY(BED_MAXTEMP), SS_LEFT);
+ STATIC_ITEM(TERN(WATCH_BED, MSG_INFO_RUNAWAY_ON, MSG_INFO_RUNAWAY_OFF), SS_LEFT);
+ #endif
+
+ #if HAS_HEATED_CHAMBER
+ #undef THERMISTOR_ID
+ #define THERMISTOR_ID TEMP_SENSOR_CHAMBER
+ #include "../thermistornames.h"
+ STATIC_ITEM_P(PSTR("CHAM: " THERMISTOR_NAME), SS_INVERT);
+ PSTRING_ITEM(MSG_INFO_MIN_TEMP, STRINGIFY(CHAMBER_MINTEMP), SS_LEFT);
+ PSTRING_ITEM(MSG_INFO_MAX_TEMP, STRINGIFY(CHAMBER_MAXTEMP), SS_LEFT);
+ STATIC_ITEM(TERN(WATCH_CHAMBER, MSG_INFO_RUNAWAY_ON, MSG_INFO_RUNAWAY_OFF), SS_LEFT);
+ #endif
+
+ END_SCREEN();
+}
+
+//
+// About Printer > Board Info
+//
+void menu_info_board() {
+ if (ui.use_click()) return ui.go_back();
+
+ START_SCREEN();
+ STATIC_ITEM_P(PSTR(BOARD_INFO_NAME), SS_DEFAULT|SS_INVERT); // MyPrinterController
+ #ifdef BOARD_WEBSITE_URL
+ STATIC_ITEM_P(PSTR(BOARD_WEBSITE_URL), SS_LEFT); // www.my3dprinter.com
+ #endif
+ PSTRING_ITEM(MSG_INFO_BAUDRATE, STRINGIFY(BAUDRATE), SS_CENTER); // Baud: 250000
+ PSTRING_ITEM(MSG_INFO_PROTOCOL, PROTOCOL_VERSION, SS_CENTER); // Protocol: 1.0
+ PSTRING_ITEM(MSG_INFO_PSU, PSU_NAME, SS_CENTER);
+ END_SCREEN();
+}
+
+//
+// About Printer > Printer Info
+//
+#if ENABLED(LCD_PRINTER_INFO_IS_BOOTSCREEN)
+
+ void menu_show_marlin_bootscreen() {
+ if (ui.use_click()) { ui.goto_previous_screen_no_defer(); }
+ ui.draw_marlin_bootscreen();
+ }
+
+ #if ENABLED(SHOW_CUSTOM_BOOTSCREEN)
+ void menu_show_custom_bootscreen() {
+ if (ui.use_click()) { ui.goto_screen(menu_show_marlin_bootscreen); }
+ ui.draw_custom_bootscreen();
+ }
+ #endif
+
+#else
+
+ void menu_info_printer() {
+ if (ui.use_click()) return ui.go_back();
+ START_SCREEN();
+ STATIC_ITEM(MSG_MARLIN, SS_DEFAULT|SS_INVERT); // Marlin
+ STATIC_ITEM_P(PSTR(SHORT_BUILD_VERSION)); // x.x.x-Branch
+ STATIC_ITEM_P(PSTR(STRING_DISTRIBUTION_DATE)); // YYYY-MM-DD HH:MM
+ STATIC_ITEM_P(PSTR(MACHINE_NAME)); // My3DPrinter
+ STATIC_ITEM_P(PSTR(WEBSITE_URL)); // www.my3dprinter.com
+ PSTRING_ITEM(MSG_INFO_EXTRUDERS, STRINGIFY(EXTRUDERS), SS_CENTER); // Extruders: 2
+ #if HAS_LEVELING
+ STATIC_ITEM(
+ TERN_(AUTO_BED_LEVELING_3POINT, MSG_3POINT_LEVELING) // 3-Point Leveling
+ TERN_(AUTO_BED_LEVELING_LINEAR, MSG_LINEAR_LEVELING) // Linear Leveling
+ TERN_(AUTO_BED_LEVELING_BILINEAR, MSG_BILINEAR_LEVELING) // Bi-linear Leveling
+ TERN_(AUTO_BED_LEVELING_UBL, MSG_UBL_LEVELING) // Unified Bed Leveling
+ TERN_(MESH_BED_LEVELING, MSG_MESH_LEVELING) // Mesh Leveling
+ );
+ #endif
+ END_SCREEN();
+ }
+
+#endif
+
+//
+// "About Printer" submenu
+//
+void menu_info() {
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+ #if ENABLED(LCD_PRINTER_INFO_IS_BOOTSCREEN)
+ SUBMENU(MSG_INFO_PRINTER_MENU, TERN(SHOW_CUSTOM_BOOTSCREEN, menu_show_custom_bootscreen, menu_show_marlin_bootscreen));
+ #else
+ SUBMENU(MSG_INFO_PRINTER_MENU, menu_info_printer); // Printer Info >
+ SUBMENU(MSG_INFO_BOARD_MENU, menu_info_board); // Board Info >
+ #if EXTRUDERS
+ SUBMENU(MSG_INFO_THERMISTOR_MENU, menu_info_thermistors); // Thermistors >
+ #endif
+ #endif
+
+ #if ENABLED(PRINTCOUNTER)
+ SUBMENU(MSG_INFO_STATS_MENU, menu_info_stats); // Printer Stats >
+ #endif
+
+ #if HAS_GAMES
+ {
+ #if ENABLED(GAMES_EASTER_EGG)
+ SKIP_ITEM(); SKIP_ITEM(); SKIP_ITEM();
+ #endif
+
+ // Game sub-menu or the individual game
+ SUBMENU(
+ #if HAS_GAME_MENU
+ MSG_GAMES, menu_game
+ #elif ENABLED(MARLIN_BRICKOUT)
+ MSG_BRICKOUT, brickout.enter_game
+ #elif ENABLED(MARLIN_INVADERS)
+ MSG_INVADERS, invaders.enter_game
+ #elif ENABLED(MARLIN_SNAKE)
+ MSG_SNAKE, snake.enter_game
+ #elif ENABLED(MARLIN_MAZE)
+ MSG_MAZE, maze.enter_game
+ #endif
+ );
+ }
+ #endif
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && LCD_INFO_MENU
diff --git a/Marlin/src/lcd/menu/menu_item.h b/Marlin/src/lcd/menu/menu_item.h
new file mode 100644
index 0000000..6873f20
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_item.h
@@ -0,0 +1,495 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "menu.h"
+#include "../marlinui.h"
+#include "../../gcode/queue.h" // for inject_P
+
+#include "../../inc/MarlinConfigPre.h"
+
+void lcd_move_z();
+
+////////////////////////////////////////////
+///////////// Base Menu Items //////////////
+////////////////////////////////////////////
+
+// SUBMENU(LABEL, screen_handler)
+class MenuItem_submenu : public MenuItemBase {
+ public:
+ FORCE_INLINE static void draw(const bool sel, const uint8_t row, PGM_P const pstr, ...) {
+ _draw(sel, row, pstr, '>', LCD_STR_ARROW_RIGHT[0]);
+ }
+ static inline void action(PGM_P const, const screenFunc_t func) { ui.save_previous_screen(); ui.goto_screen(func); }
+};
+
+// Any menu item that invokes an immediate action
+class MenuItem_button : public MenuItemBase {
+ public:
+ // Button-y Items are selectable lines with no other indicator
+ static inline void draw(const bool sel, const uint8_t row, PGM_P const pstr, ...) {
+ _draw(sel, row, pstr, '>', ' ');
+ }
+};
+
+// ACTION_ITEM(LABEL, FUNC)
+class MenuItem_function : public MenuItem_button {
+ public:
+ //static inline void action(PGM_P const, const uint8_t, const menuAction_t func) { (*func)(); };
+ static inline void action(PGM_P const, const menuAction_t func) { (*func)(); };
+};
+
+// GCODES_ITEM(LABEL, GCODES)
+class MenuItem_gcode : public MenuItem_button {
+ public:
+ FORCE_INLINE static void draw(const bool sel, const uint8_t row, PGM_P const pstr, ...) {
+ _draw(sel, row, pstr, '>', ' ');
+ }
+ static void action(PGM_P const, PGM_P const pgcode) { queue.inject_P(pgcode); }
+ static inline void action(PGM_P const pstr, const uint8_t, const char * const pgcode) { action(pstr, pgcode); }
+};
+
+////////////////////////////////////////////
+///////////// Edit Menu Items //////////////
+////////////////////////////////////////////
+
+// Template for specific Menu Edit Item Types
+template<typename NAME>
+class TMenuEditItem : MenuEditItemBase {
+ private:
+ typedef typename NAME::type_t type_t;
+ static inline float scale(const float value) { return NAME::scale(value); }
+ static inline float unscale(const float value) { return NAME::unscale(value); }
+ static const char* to_string(const int32_t value) { return NAME::strfunc(unscale(value)); }
+ static void load(void *ptr, const int32_t value) { *((type_t*)ptr) = unscale(value); }
+ public:
+ FORCE_INLINE static void draw(const bool sel, const uint8_t row, PGM_P const pstr, type_t * const data, ...) {
+ MenuEditItemBase::draw(sel, row, pstr, NAME::strfunc(*(data)));
+ }
+ FORCE_INLINE static void draw(const bool sel, const uint8_t row, PGM_P const pstr, type_t (*pget)(), ...) {
+ MenuEditItemBase::draw(sel, row, pstr, NAME::strfunc(pget()));
+ }
+ // Edit screen for this type of item
+ static void edit_screen() { MenuEditItemBase::edit_screen(to_string, load); }
+ static void action(
+ PGM_P const pstr, // Edit label
+ type_t * const ptr, // Value pointer
+ const type_t minValue, // Value range
+ const type_t maxValue,
+ const screenFunc_t callback=nullptr, // Value update callback
+ const bool live=false // Callback during editing
+ ) {
+ // Make sure minv and maxv fit within int32_t
+ const int32_t minv = _MAX(scale(minValue), INT32_MIN),
+ maxv = _MIN(scale(maxValue), INT32_MAX);
+ goto_edit_screen(pstr, ptr, minv, maxv - minv, scale(*ptr) - minv,
+ edit_screen, callback, live);
+ }
+};
+
+// Provide a set of Edit Item Types which encompass a primitive
+// type, a string function, and a scale factor for edit and display.
+// These items call the Edit Item draw method passing the prepared string.
+#define __DOFIXfloat PROBE()
+#define _DOFIX(TYPE,V) TYPE(TERN(IS_PROBE(__DOFIX##TYPE),FIXFLOAT(V),(V)))
+#define DEFINE_MENU_EDIT_ITEM_TYPE(NAME, TYPE, STRFUNC, SCALE, V...) \
+ struct MenuEditItemInfo_##NAME { \
+ typedef TYPE type_t; \
+ static inline float scale(const float value) { return value * (SCALE) + (V+0); } \
+ static inline float unscale(const float value) { return value / (SCALE) + (V+0); } \
+ static inline const char* strfunc(const float value) { return STRFUNC(_DOFIX(TYPE,value)); } \
+ }; \
+ typedef TMenuEditItem<MenuEditItemInfo_##NAME> MenuItem_##NAME
+
+// NAME TYPE STRFUNC SCALE +ROUND
+DEFINE_MENU_EDIT_ITEM_TYPE(percent ,uint8_t ,ui8tostr4pctrj , 100.f/255.f, 0.5f); // 100% right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(percent_3 ,uint8_t ,pcttostrpctrj , 1 ); // 100% right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(int3 ,int16_t ,i16tostr3rj , 1 ); // 123, -12 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(int4 ,int16_t ,i16tostr4signrj , 1 ); // 1234, -123 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(int8 ,int8_t ,i8tostr3rj , 1 ); // 123, -12 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(uint8 ,uint8_t ,ui8tostr3rj , 1 ); // 123 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(uint16_3 ,uint16_t ,ui16tostr3rj , 1 ); // 123 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(uint16_4 ,uint16_t ,ui16tostr4rj , 0.1f ); // 1234 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(uint16_5 ,uint16_t ,ui16tostr5rj , 0.01f ); // 12345 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(float3 ,float ,ftostr3 , 1 ); // 123 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(float42_52 ,float ,ftostr42_52 , 100 ); // _2.34, 12.34, -2.34 or 123.45, -23.45
+DEFINE_MENU_EDIT_ITEM_TYPE(float43 ,float ,ftostr43sign ,1000 ); // -1.234, _1.234, +1.234
+DEFINE_MENU_EDIT_ITEM_TYPE(float4 ,float ,ftostr4sign , 1 ); // 1234 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(float5 ,float ,ftostr5rj , 1 ); // 12345 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(float5_25 ,float ,ftostr5rj , 0.04f ); // 12345 right-justified (25 increment)
+DEFINE_MENU_EDIT_ITEM_TYPE(float51 ,float ,ftostr51rj , 10 ); // 1234.5 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(float31sign ,float ,ftostr31sign , 10 ); // +12.3
+DEFINE_MENU_EDIT_ITEM_TYPE(float41sign ,float ,ftostr41sign , 10 ); // +123.4
+DEFINE_MENU_EDIT_ITEM_TYPE(float51sign ,float ,ftostr51sign , 10 ); // +1234.5
+DEFINE_MENU_EDIT_ITEM_TYPE(float52sign ,float ,ftostr52sign , 100 ); // +123.45
+DEFINE_MENU_EDIT_ITEM_TYPE(long5 ,uint32_t ,ftostr5rj , 0.01f ); // 12345 right-justified
+DEFINE_MENU_EDIT_ITEM_TYPE(long5_25 ,uint32_t ,ftostr5rj , 0.04f ); // 12345 right-justified (25 increment)
+
+#if HAS_BED_PROBE
+ #if Z_PROBE_OFFSET_RANGE_MIN >= -9 && Z_PROBE_OFFSET_RANGE_MAX <= 9
+ #define LCD_Z_OFFSET_TYPE float43 // Values from -9.000 to +9.000
+ #else
+ #define LCD_Z_OFFSET_TYPE float42_52 // Values from -99.99 to 99.99
+ #endif
+#endif
+
+class MenuItem_bool : public MenuEditItemBase {
+ public:
+ FORCE_INLINE static void draw(const bool sel, const uint8_t row, PGM_P const pstr, const bool onoff) {
+ MenuEditItemBase::draw(sel, row, pstr, onoff ? GET_TEXT(MSG_LCD_ON) : GET_TEXT(MSG_LCD_OFF), true);
+ }
+ FORCE_INLINE static void draw(const bool sel, const uint8_t row, PGM_P const pstr, bool * const data, ...) {
+ draw(sel, row, pstr, *data);
+ }
+ FORCE_INLINE static void draw(const bool sel, const uint8_t row, PGM_P const pstr, PGM_P const, bool (*pget)(), ...) {
+ draw(sel, row, pstr, pget());
+ }
+ static void action(PGM_P const pstr, bool * const ptr, const screenFunc_t callbackFunc=nullptr) {
+ *ptr ^= true; ui.refresh();
+ if (callbackFunc) (*callbackFunc)();
+ }
+};
+
+/**
+ * ////////////////////////////////////////////
+ * //////////// Menu System Macros ////////////
+ * ////////////////////////////////////////////
+ *
+ * Marlin's native menu screens work by running a loop from the top visible line index
+ * to the bottom visible line index (according to how much the screen has been scrolled).
+ * This complete loop is done on every menu screen call.
+ *
+ * The menu system is highly dynamic, so it doesn't know ahead of any menu loop which
+ * items will be visible or hidden, so menu items don't have a fixed index number.
+ *
+ * During the loop, each menu item checks to see if its line is the current one. If it is,
+ * then it checks to see if a click has arrived so it can run its action. If the action
+ * doesn't redirect to another screen then the menu item calls its draw method.
+ *
+ * Menu item add-ons can do whatever they like.
+ *
+ * This mixture of drawing and processing inside a loop has the advantage that a single
+ * line can be used to represent a menu item, and that is the rationale for this design.
+ *
+ * One of the pitfalls of this method is that DOGM displays call the screen handler 2x,
+ * 4x, or 8x per screen update to draw just one segment of the screen. As a result, any
+ * menu item that exists in two screen segments is drawn and processed twice per screen
+ * update. With each item processed 5, 10, 20, or 40 times the logic has to be simple.
+ *
+ * To avoid repetition and side-effects, function calls for testing menu item conditions
+ * should be done before the menu loop (START_MENU / START_SCREEN).
+ */
+
+/**
+ * SCREEN_OR_MENU_LOOP generates header code for a screen or menu
+ *
+ * encoderTopLine is the top menu line to display
+ * _lcdLineNr is the index of the LCD line (e.g., 0-3)
+ * _menuLineNr is the menu item to draw and process
+ * _thisItemNr is the index of each MENU_ITEM or STATIC_ITEM
+ */
+#define SCREEN_OR_MENU_LOOP(IS_MENU) \
+ scroll_screen(IS_MENU ? 1 : LCD_HEIGHT, IS_MENU); \
+ int8_t _menuLineNr = encoderTopLine, _thisItemNr = 0; \
+ bool _skipStatic = IS_MENU; UNUSED(_thisItemNr); \
+ for (int8_t _lcdLineNr = 0; _lcdLineNr < LCD_HEIGHT; _lcdLineNr++, _menuLineNr++) { \
+ _thisItemNr = 0
+
+/**
+ * START_SCREEN Opening code for a screen having only static items.
+ * Do simplified scrolling of the entire screen.
+ *
+ * START_MENU Opening code for a screen with menu items.
+ * Scroll as-needed to keep the selected line in view.
+ */
+#define START_SCREEN() SCREEN_OR_MENU_LOOP(false)
+#define START_MENU() SCREEN_OR_MENU_LOOP(true)
+#define NEXT_ITEM() (++_thisItemNr)
+#define SKIP_ITEM() NEXT_ITEM()
+#define END_SCREEN() } screen_items = _thisItemNr
+#define END_MENU() END_SCREEN(); UNUSED(_skipStatic)
+
+/**
+ * MENU_ITEM generates draw & handler code for a menu item, potentially calling:
+ *
+ * MenuItem_<type>::draw(sel, row, label, arg3...)
+ * MenuItem_<type>::action(arg3...)
+ *
+ * Examples:
+ * BACK_ITEM(MSG_INFO_SCREEN)
+ * MenuItem_back::action(plabel, ...)
+ * MenuItem_back::draw(sel, row, plabel, ...)
+ *
+ * ACTION_ITEM(MSG_PAUSE_PRINT, lcd_sdcard_pause)
+ * MenuItem_function::action(plabel, lcd_sdcard_pause)
+ * MenuItem_function::draw(sel, row, plabel, lcd_sdcard_pause)
+ *
+ * EDIT_ITEM(int3, MSG_SPEED, &feedrate_percentage, 10, 999)
+ * MenuItem_int3::action(plabel, &feedrate_percentage, 10, 999)
+ * MenuItem_int3::draw(sel, row, plabel, &feedrate_percentage, 10, 999)
+ */
+
+#if ENABLED(ENCODER_RATE_MULTIPLIER)
+ #define _MENU_ITEM_MULTIPLIER_CHECK(USE_MULTIPLIER) do{ if (USE_MULTIPLIER) ui.enable_encoder_multiplier(true); }while(0)
+#else
+ #define _MENU_ITEM_MULTIPLIER_CHECK(USE_MULTIPLIER)
+#endif
+
+#define _MENU_INNER_P(TYPE, USE_MULTIPLIER, PLABEL, V...) do { \
+ PGM_P const plabel = PLABEL; \
+ if (encoderLine == _thisItemNr && ui.use_click()) { \
+ _MENU_ITEM_MULTIPLIER_CHECK(USE_MULTIPLIER); \
+ MenuItem_##TYPE::action(plabel, ##V); \
+ if (ui.screen_changed) return; \
+ } \
+ if (ui.should_draw()) \
+ MenuItem_##TYPE::draw \
+ (encoderLine == _thisItemNr, _lcdLineNr, plabel, ##V); \
+}while(0)
+
+#define _MENU_ITEM_P(TYPE, V...) do { \
+ if (_menuLineNr == _thisItemNr) { \
+ _skipStatic = false; \
+ _MENU_INNER_P(TYPE, ##V); \
+ } \
+ NEXT_ITEM(); \
+}while(0)
+
+// Indexed items set a global index value and optional data
+#define _MENU_ITEM_N_S_P(TYPE, N, S, V...) do{ \
+ if (_menuLineNr == _thisItemNr) { \
+ _skipStatic = false; \
+ MenuItemBase::init(N, S); \
+ _MENU_INNER_P(TYPE, ##V); \
+ } \
+ NEXT_ITEM(); \
+}while(0)
+
+// Indexed items set a global index value
+#define _MENU_ITEM_N_P(TYPE, N, V...) do{ \
+ if (_menuLineNr == _thisItemNr) { \
+ _skipStatic = false; \
+ MenuItemBase::itemIndex = N; \
+ _MENU_INNER_P(TYPE, ##V); \
+ } \
+ NEXT_ITEM(); \
+}while(0)
+
+// Items with a unique string
+#define _MENU_ITEM_S_P(TYPE, S, V...) do{ \
+ if (_menuLineNr == _thisItemNr) { \
+ _skipStatic = false; \
+ MenuItemBase::itemString = S; \
+ _MENU_INNER_P(TYPE, ##V); \
+ } \
+ NEXT_ITEM(); \
+}while(0)
+
+// STATIC_ITEM draws a styled string with no highlight.
+// Parameters: label [, style [, char *value] ]
+
+#define STATIC_ITEM_INNER_P(PLABEL, V...) do{ \
+ if (_skipStatic && encoderLine <= _thisItemNr) { \
+ ui.encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \
+ ++encoderLine; \
+ } \
+ if (ui.should_draw()) \
+ MenuItem_static::draw(_lcdLineNr, PLABEL, ##V); \
+} while(0)
+
+#define STATIC_ITEM_P(PLABEL, V...) do{ \
+ if (_menuLineNr == _thisItemNr) \
+ STATIC_ITEM_INNER_P(PLABEL, ##V); \
+ NEXT_ITEM(); \
+} while(0)
+
+#define STATIC_ITEM_N_P(PLABEL, N, V...) do{ \
+ if (_menuLineNr == _thisItemNr) { \
+ MenuItemBase::init(N); \
+ STATIC_ITEM_INNER_P(PLABEL, ##V); \
+ } \
+ NEXT_ITEM(); \
+}while(0)
+
+// PSTRING_ITEM is like STATIC_ITEM but it takes
+// two PSTRs with the style as the last parameter.
+
+#define PSTRING_ITEM_P(PLABEL, PVAL, STYL) do{ \
+ constexpr int m = 20; \
+ char msg[m+1]; \
+ msg[0] = ':'; msg[1] = ' '; \
+ strncpy_P(msg+2, PSTR(PVAL), m-2); \
+ if (msg[m-1] & 0x80) msg[m-1] = '\0'; \
+ STATIC_ITEM_P(PLABEL, STYL, msg); \
+}while(0)
+
+#define PSTRING_ITEM(LABEL, V...) PSTRING_ITEM_P(GET_TEXT(LABEL), ##V)
+
+#define STATIC_ITEM(LABEL, V...) STATIC_ITEM_P(GET_TEXT(LABEL), ##V)
+#define STATIC_ITEM_N(LABEL, N, V...) STATIC_ITEM_N_P(GET_TEXT(LABEL), N, ##V)
+
+#define MENU_ITEM_N_S_P(TYPE, N, S, PLABEL, V...) _MENU_ITEM_N_S_P(TYPE, N, S, false, PLABEL, ##V)
+#define MENU_ITEM_N_S(TYPE, N, S, LABEL, V...) MENU_ITEM_N_S_P(TYPE, N, S, GET_TEXT(LABEL), ##V)
+#define MENU_ITEM_S_P(TYPE, S, PLABEL, V...) _MENU_ITEM_S_P(TYPE, S, false, PLABEL, ##V)
+#define MENU_ITEM_S(TYPE, S, LABEL, V...) MENU_ITEM_S_P(TYPE, S, GET_TEXT(LABEL), ##V)
+#define MENU_ITEM_N_P(TYPE, N, PLABEL, V...) _MENU_ITEM_N_P(TYPE, N, false, PLABEL, ##V)
+#define MENU_ITEM_N(TYPE, N, LABEL, V...) MENU_ITEM_N_P(TYPE, N, GET_TEXT(LABEL), ##V)
+#define MENU_ITEM_P(TYPE, PLABEL, V...) _MENU_ITEM_P(TYPE, false, PLABEL, ##V)
+#define MENU_ITEM(TYPE, LABEL, V...) MENU_ITEM_P(TYPE, GET_TEXT(LABEL), ##V)
+
+#define BACK_ITEM_P(PLABEL) MENU_ITEM_P(back, PLABEL)
+#define BACK_ITEM(LABEL) MENU_ITEM(back, LABEL)
+
+#define ACTION_ITEM_N_S_P(N, S, PLABEL, ACTION) MENU_ITEM_N_S_P(function, N, S, PLABEL, ACTION)
+#define ACTION_ITEM_N_S(N, S, LABEL, ACTION) ACTION_ITEM_N_S_P(N, S, GET_TEXT(LABEL), ACTION)
+#define ACTION_ITEM_S_P(S, PLABEL, ACTION) MENU_ITEM_S_P(function, S, PLABEL, ACTION)
+#define ACTION_ITEM_S(S, LABEL, ACTION) ACTION_ITEM_S_P(S, GET_TEXT(LABEL), ACTION)
+#define ACTION_ITEM_N_P(N, PLABEL, ACTION) MENU_ITEM_N_P(function, N, PLABEL, ACTION)
+#define ACTION_ITEM_N(N, LABEL, ACTION) ACTION_ITEM_N_P(N, GET_TEXT(LABEL), ACTION)
+#define ACTION_ITEM_P(PLABEL, ACTION) MENU_ITEM_P(function, PLABEL, ACTION)
+#define ACTION_ITEM(LABEL, ACTION) ACTION_ITEM_P(GET_TEXT(LABEL), ACTION)
+
+#define GCODES_ITEM_N_S_P(N, S, PLABEL, GCODES) MENU_ITEM_N_S_P(gcode, N, S, PLABEL, GCODES)
+#define GCODES_ITEM_N_S(N, S, LABEL, GCODES) GCODES_ITEM_N_S_P(N, S, GET_TEXT(LABEL), GCODES)
+#define GCODES_ITEM_S_P(S, PLABEL, GCODES) MENU_ITEM_S_P(gcode, S, PLABEL, GCODES)
+#define GCODES_ITEM_S(S, LABEL, GCODES) GCODES_ITEM_S_P(S, GET_TEXT(LABEL), GCODES)
+#define GCODES_ITEM_N_P(N, PLABEL, GCODES) MENU_ITEM_N_P(gcode, N, PLABEL, GCODES)
+#define GCODES_ITEM_N(N, LABEL, GCODES) GCODES_ITEM_N_P(N, GET_TEXT(LABEL), GCODES)
+#define GCODES_ITEM_P(PLABEL, GCODES) MENU_ITEM_P(gcode, PLABEL, GCODES)
+#define GCODES_ITEM(LABEL, GCODES) GCODES_ITEM_P(GET_TEXT(LABEL), GCODES)
+
+#define SUBMENU_N_S_P(N, S, PLABEL, DEST) MENU_ITEM_N_S_P(submenu, N, S, PLABEL, DEST)
+#define SUBMENU_N_S(N, S, LABEL, DEST) SUBMENU_N_S_P(N, S, GET_TEXT(LABEL), DEST)
+#define SUBMENU_S_P(S, PLABEL, DEST) MENU_ITEM_S_P(submenu, S, PLABEL, DEST)
+#define SUBMENU_S(S, LABEL, DEST) SUBMENU_S_P(S, GET_TEXT(LABEL), DEST)
+#define SUBMENU_N_P(N, PLABEL, DEST) MENU_ITEM_N_P(submenu, N, PLABEL, DEST)
+#define SUBMENU_N(N, LABEL, DEST) SUBMENU_N_P(N, GET_TEXT(LABEL), DEST)
+#define SUBMENU_P(PLABEL, DEST) MENU_ITEM_P(submenu, PLABEL, DEST)
+#define SUBMENU(LABEL, DEST) SUBMENU_P(GET_TEXT(LABEL), DEST)
+
+#define EDIT_ITEM_N_S_P(TYPE, N, S, PLABEL, V...) MENU_ITEM_N_S_P(TYPE, N, S, PLABEL, ##V)
+#define EDIT_ITEM_N_S(TYPE, N, S, LABEL, V...) EDIT_ITEM_N_S_P(TYPE, N, S, GET_TEXT(LABEL), ##V)
+#define EDIT_ITEM_S_P(TYPE, S, PLABEL, V...) MENU_ITEM_S_P(TYPE, S, PLABEL, ##V)
+#define EDIT_ITEM_S(TYPE, S, LABEL, V...) EDIT_ITEM_S_P(TYPE, S, GET_TEXT(LABEL), ##V)
+#define EDIT_ITEM_N_P(TYPE, N, PLABEL, V...) MENU_ITEM_N_P(TYPE, N, PLABEL, ##V)
+#define EDIT_ITEM_N(TYPE, N, LABEL, V...) EDIT_ITEM_N_P(TYPE, N, GET_TEXT(LABEL), ##V)
+#define EDIT_ITEM_P(TYPE, PLABEL, V...) MENU_ITEM_P(TYPE, PLABEL, ##V)
+#define EDIT_ITEM(TYPE, LABEL, V...) EDIT_ITEM_P(TYPE, GET_TEXT(LABEL), ##V)
+
+#define EDIT_ITEM_FAST_N_S_P(TYPE, N, S, PLABEL, V...) _MENU_ITEM_N_S_P(TYPE, N, S, true, PLABEL, ##V)
+#define EDIT_ITEM_FAST_N_S(TYPE, N, S, LABEL, V...) EDIT_ITEM_FAST_N_S_P(TYPE, N, S, true, GET_TEXT(LABEL), ##V)
+#define EDIT_ITEM_FAST_S_P(TYPE, S, PLABEL, V...) _MENU_ITEM_S_P(TYPE, S, true, PLABEL, ##V)
+#define EDIT_ITEM_FAST_S(TYPE, S, LABEL, V...) EDIT_ITEM_FAST_S_P(TYPE, S, GET_TEXT(LABEL), ##V)
+#define EDIT_ITEM_FAST_N_P(TYPE, N, PLABEL, V...) _MENU_ITEM_N_P(TYPE, N, true, PLABEL, ##V)
+#define EDIT_ITEM_FAST_N(TYPE, N, LABEL, V...) EDIT_ITEM_FAST_N_P(TYPE, N, GET_TEXT(LABEL), ##V)
+#define EDIT_ITEM_FAST_P(TYPE, PLABEL, V...) _MENU_ITEM_P(TYPE, true, PLABEL, ##V)
+#define EDIT_ITEM_FAST(TYPE, LABEL, V...) EDIT_ITEM_FAST_P(TYPE, GET_TEXT(LABEL), ##V)
+
+#define _CONFIRM_ITEM_INNER_P(PLABEL, V...) do { \
+ if (encoderLine == _thisItemNr && ui.use_click()) { \
+ ui.save_previous_screen(); \
+ ui.goto_screen([]{MenuItem_confirm::select_screen(V);}); \
+ return; \
+ } \
+ if (ui.should_draw()) MenuItem_confirm::draw \
+ (encoderLine == _thisItemNr, _lcdLineNr, PLABEL, ##V); \
+}while(0)
+
+// Indexed items set a global index value and optional data
+#define _CONFIRM_ITEM_P(PLABEL, V...) do { \
+ if (_menuLineNr == _thisItemNr) { \
+ _skipStatic = false; \
+ _CONFIRM_ITEM_INNER_P(PLABEL, ##V); \
+ } \
+ NEXT_ITEM(); \
+}while(0)
+
+// Indexed items set a global index value
+#define _CONFIRM_ITEM_N_S_P(N, S, V...) do{ \
+ if (_menuLineNr == _thisItemNr) { \
+ _skipStatic = false; \
+ MenuItemBase::init(N, S); \
+ _CONFIRM_ITEM_INNER_P(TYPE, ##V); \
+ } \
+ NEXT_ITEM(); \
+}while(0)
+
+// Indexed items set a global index value
+#define _CONFIRM_ITEM_N_P(N, V...) _CONFIRM_ITEM_N_S_P(N, nullptr, V)
+
+#define CONFIRM_ITEM_P(PLABEL,A,B,V...) _CONFIRM_ITEM_P(PLABEL, GET_TEXT(A), GET_TEXT(B), ##V)
+#define CONFIRM_ITEM(LABEL, V...) CONFIRM_ITEM_P(GET_TEXT(LABEL), ##V)
+
+#define YESNO_ITEM_P(PLABEL, V...) _CONFIRM_ITEM_P(PLABEL, ##V)
+#define YESNO_ITEM(LABEL, V...) YESNO_ITEM_P(GET_TEXT(LABEL), ##V)
+
+#define CONFIRM_ITEM_N_S_P(N,S,PLABEL,A,B,V...) _CONFIRM_ITEM_N_S_P(N, S, PLABEL, GET_TEXT(A), GET_TEXT(B), ##V)
+#define CONFIRM_ITEM_N_S(N,S,LABEL,V...) CONFIRM_ITEM_N_S_P(N, S, GET_TEXT(LABEL), ##V)
+#define CONFIRM_ITEM_N_P(N,PLABEL,A,B,V...) _CONFIRM_ITEM_N_P(N, PLABEL, GET_TEXT(A), GET_TEXT(B), ##V)
+#define CONFIRM_ITEM_N(N,LABEL, V...) CONFIRM_ITEM_N_P(N, GET_TEXT(LABEL), ##V)
+
+#define YESNO_ITEM_N_S_P(N,S,PLABEL, V...) _CONFIRM_ITEM_N_S_P(N, S, PLABEL, ##V)
+#define YESNO_ITEM_N_S(N,S,LABEL, V...) YESNO_ITEM_N_S_P(N, S, GET_TEXT(LABEL), ##V)
+#define YESNO_ITEM_N_P(N,PLABEL, V...) _CONFIRM_ITEM_N_P(N, PLABEL, ##V)
+#define YESNO_ITEM_N(N,LABEL, V...) YESNO_ITEM_N_P(N, GET_TEXT(LABEL), ##V)
+
+#if ENABLED(LEVEL_BED_CORNERS)
+ void _lcd_level_bed_corners();
+#endif
+
+#if HAS_FAN
+
+ #include "../../module/temperature.h"
+
+ inline void on_fan_update() {
+ thermalManager.set_fan_speed(MenuItemBase::itemIndex, editable.uint8);
+ }
+
+ #if ENABLED(EXTRA_FAN_SPEED)
+ #define EDIT_EXTRA_FAN_SPEED(V...) EDIT_ITEM_FAST_N(V)
+ #else
+ #define EDIT_EXTRA_FAN_SPEED(...)
+ #endif
+
+ #define _FAN_EDIT_ITEMS(F,L) do{ \
+ editable.uint8 = thermalManager.fan_speed[F]; \
+ EDIT_ITEM_FAST_N(percent, F, MSG_##L, &editable.uint8, 0, 255, on_fan_update); \
+ EDIT_EXTRA_FAN_SPEED(percent, F, MSG_EXTRA_##L, &thermalManager.new_fan_speed[F], 3, 255); \
+ }while(0)
+
+ #if FAN_COUNT > 1
+ #define FAN_EDIT_ITEMS(F) _FAN_EDIT_ITEMS(F,FAN_SPEED_N)
+ #endif
+
+ #define SNFAN(N) (ENABLED(SINGLENOZZLE_STANDBY_FAN) && !HAS_FAN##N && EXTRUDERS > N)
+
+ #if SNFAN(1) || SNFAN(2) || SNFAN(3) || SNFAN(4) || SNFAN(5) || SNFAN(6) || SNFAN(7)
+ #define DEFINE_SINGLENOZZLE_ITEM() \
+ auto singlenozzle_item = [&](const uint8_t f) { \
+ editable.uint8 = thermalManager.singlenozzle_fan_speed[f]; \
+ EDIT_ITEM_FAST_N(percent, f, MSG_STORED_FAN_N, &editable.uint8, 0, 255, on_fan_update); \
+ }
+ #else
+ #define DEFINE_SINGLENOZZLE_ITEM() NOOP
+ #endif
+
+#endif // HAS_FAN
diff --git a/Marlin/src/lcd/menu/menu_job_recovery.cpp b/Marlin/src/lcd/menu/menu_job_recovery.cpp
new file mode 100644
index 0000000..7cd2949
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_job_recovery.cpp
@@ -0,0 +1,57 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Job Recovery Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, POWER_LOSS_RECOVERY)
+
+#include "menu_item.h"
+#include "../../gcode/queue.h"
+#include "../../sd/cardreader.h"
+#include "../../feature/powerloss.h"
+
+static void lcd_power_loss_recovery_resume() {
+ ui.return_to_status();
+ queue.inject_P(PSTR("M1000"));
+}
+
+void lcd_power_loss_recovery_cancel() {
+ recovery.cancel();
+ ui.return_to_status();
+}
+
+// TODO: Display long filename with Cancel/Resume buttons
+// Requires supporting methods in PLR class.
+void menu_job_recovery() {
+ ui.defer_status_screen();
+ START_MENU();
+ STATIC_ITEM(MSG_OUTAGE_RECOVERY);
+ ACTION_ITEM(MSG_RESUME_PRINT, lcd_power_loss_recovery_resume);
+ ACTION_ITEM(MSG_STOP_PRINT, lcd_power_loss_recovery_cancel);
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && POWER_LOSS_RECOVERY
diff --git a/Marlin/src/lcd/menu/menu_language.cpp b/Marlin/src/lcd/menu/menu_language.cpp
new file mode 100644
index 0000000..4c4b788
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_language.cpp
@@ -0,0 +1,59 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Language Selection Menu
+//
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_MULTI_LANGUAGE
+
+#include "menu_item.h"
+#include "../../MarlinCore.h"
+#include "../../module/settings.h"
+
+static void set_lcd_language(const uint8_t inlang) {
+ ui.set_language(inlang);
+ TERN_(LCD_LANGUAGE_AUTO_SAVE, (void)settings.save());
+}
+
+void menu_language() {
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ MENU_ITEM_P(function, GET_LANG(LCD_LANGUAGE )::LANGUAGE, []{ set_lcd_language(0); });
+ MENU_ITEM_P(function, GET_LANG(LCD_LANGUAGE_2)::LANGUAGE, []{ set_lcd_language(1); });
+ #if NUM_LANGUAGES > 2
+ MENU_ITEM_P(function, GET_LANG(LCD_LANGUAGE_3)::LANGUAGE, []{ set_lcd_language(2); });
+ #if NUM_LANGUAGES > 3
+ MENU_ITEM_P(function, GET_LANG(LCD_LANGUAGE_4)::LANGUAGE, []{ set_lcd_language(3); });
+ #if NUM_LANGUAGES > 4
+ MENU_ITEM_P(function, GET_LANG(LCD_LANGUAGE_5)::LANGUAGE, []{ set_lcd_language(4); });
+ #endif
+ #endif
+ #endif
+
+ END_MENU();
+}
+
+#endif // HAS_MULTI_LANGUAGE
diff --git a/Marlin/src/lcd/menu/menu_led.cpp b/Marlin/src/lcd/menu/menu_led.cpp
new file mode 100644
index 0000000..552c03a
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_led.cpp
@@ -0,0 +1,159 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// LED Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_LCD_MENU && EITHER(LED_CONTROL_MENU, CASE_LIGHT_MENU)
+
+#include "menu_item.h"
+
+#if ENABLED(LED_CONTROL_MENU)
+ #include "../../feature/leds/leds.h"
+
+ #if ENABLED(LED_COLOR_PRESETS)
+
+ void menu_led_presets() {
+ START_MENU();
+ #if LCD_HEIGHT > 2
+ STATIC_ITEM(MSG_LED_PRESETS, SS_DEFAULT|SS_INVERT);
+ #endif
+ BACK_ITEM(MSG_LED_CONTROL);
+ ACTION_ITEM(MSG_SET_LEDS_WHITE, leds.set_white);
+ ACTION_ITEM(MSG_SET_LEDS_RED, leds.set_red);
+ ACTION_ITEM(MSG_SET_LEDS_ORANGE, leds.set_orange);
+ ACTION_ITEM(MSG_SET_LEDS_YELLOW, leds.set_yellow);
+ ACTION_ITEM(MSG_SET_LEDS_GREEN, leds.set_green);
+ ACTION_ITEM(MSG_SET_LEDS_BLUE, leds.set_blue);
+ ACTION_ITEM(MSG_SET_LEDS_INDIGO, leds.set_indigo);
+ ACTION_ITEM(MSG_SET_LEDS_VIOLET, leds.set_violet);
+ END_MENU();
+ }
+
+ #endif
+
+ #if ENABLED(NEO2_COLOR_PRESETS)
+
+ void menu_leds2_presets() {
+ START_MENU();
+ #if LCD_HEIGHT > 2
+ STATIC_ITEM(MSG_NEO2_PRESETS, SS_DEFAULT|SS_INVERT);
+ #endif
+ BACK_ITEM(MSG_LED_CONTROL);
+ ACTION_ITEM(MSG_SET_LEDS_WHITE, leds2.set_white);
+ ACTION_ITEM(MSG_SET_LEDS_RED, leds2.set_red);
+ ACTION_ITEM(MSG_SET_LEDS_ORANGE, leds2.set_orange);
+ ACTION_ITEM(MSG_SET_LEDS_YELLOW, leds2.set_yellow);
+ ACTION_ITEM(MSG_SET_LEDS_GREEN, leds2.set_green);
+ ACTION_ITEM(MSG_SET_LEDS_BLUE, leds2.set_blue);
+ ACTION_ITEM(MSG_SET_LEDS_INDIGO, leds2.set_indigo);
+ ACTION_ITEM(MSG_SET_LEDS_VIOLET, leds2.set_violet);
+ END_MENU();
+ }
+
+ #endif
+
+ void menu_led_custom() {
+ START_MENU();
+ BACK_ITEM(MSG_LED_CONTROL);
+ #if ENABLED(NEOPIXEL2_SEPARATE)
+ STATIC_ITEM_N(MSG_LED_CHANNEL_N, 1, SS_DEFAULT|SS_INVERT);
+ #endif
+ EDIT_ITEM(uint8, MSG_INTENSITY_R, &leds.color.r, 0, 255, leds.update, true);
+ EDIT_ITEM(uint8, MSG_INTENSITY_G, &leds.color.g, 0, 255, leds.update, true);
+ EDIT_ITEM(uint8, MSG_INTENSITY_B, &leds.color.b, 0, 255, leds.update, true);
+ #if EITHER(RGBW_LED, NEOPIXEL_LED)
+ EDIT_ITEM(uint8, MSG_INTENSITY_W, &leds.color.w, 0, 255, leds.update, true);
+ #if ENABLED(NEOPIXEL_LED)
+ EDIT_ITEM(uint8, MSG_LED_BRIGHTNESS, &leds.color.i, 0, 255, leds.update, true);
+ #endif
+ #endif
+ #if ENABLED(NEOPIXEL2_SEPARATE)
+ STATIC_ITEM_N(MSG_LED_CHANNEL_N, 2, SS_DEFAULT|SS_INVERT);
+ EDIT_ITEM(uint8, MSG_INTENSITY_R, &leds2.color.r, 0, 255, leds2.update, true);
+ EDIT_ITEM(uint8, MSG_INTENSITY_G, &leds2.color.g, 0, 255, leds2.update, true);
+ EDIT_ITEM(uint8, MSG_INTENSITY_B, &leds2.color.b, 0, 255, leds2.update, true);
+ EDIT_ITEM(uint8, MSG_INTENSITY_W, &leds2.color.w, 0, 255, leds2.update, true);
+ EDIT_ITEM(uint8, MSG_NEO2_BRIGHTNESS, &leds2.color.i, 0, 255, leds2.update, true);
+ #endif
+ END_MENU();
+ }
+#endif
+
+#if ENABLED(CASE_LIGHT_MENU)
+ #include "../../feature/caselight.h"
+
+ #if CASELIGHT_USES_BRIGHTNESS
+ void menu_case_light() {
+ START_MENU();
+ BACK_ITEM(MSG_CONFIGURATION);
+ EDIT_ITEM(percent, MSG_CASE_LIGHT_BRIGHTNESS, &caselight.brightness, 0, 255, caselight.update_brightness, true);
+ EDIT_ITEM(bool, MSG_CASE_LIGHT, (bool*)&caselight.on, caselight.update_enabled);
+ END_MENU();
+ }
+ #endif
+#endif
+
+void menu_led() {
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ #if ENABLED(LED_CONTROL_MENU)
+ editable.state = leds.lights_on;
+ EDIT_ITEM(bool, MSG_LEDS, &editable.state, leds.toggle);
+ #if ENABLED(LED_COLOR_PRESETS)
+ ACTION_ITEM(MSG_SET_LEDS_DEFAULT, leds.set_default);
+ #endif
+ #if ENABLED(NEOPIXEL2_SEPARATE)
+ editable.state = leds2.lights_on;
+ EDIT_ITEM(bool, MSG_LEDS2, &editable.state, leds2.toggle);
+ #if ENABLED(NEO2_COLOR_PRESETS)
+ ACTION_ITEM(MSG_SET_LEDS_DEFAULT, leds2.set_default);
+ #endif
+ #endif
+ #if ENABLED(LED_COLOR_PRESETS)
+ SUBMENU(MSG_LED_PRESETS, menu_led_presets);
+ #endif
+ #if ENABLED(NEO2_COLOR_PRESETS)
+ SUBMENU(MSG_NEO2_PRESETS, menu_leds2_presets);
+ #endif
+ SUBMENU(MSG_CUSTOM_LEDS, menu_led_custom);
+ #endif
+
+ //
+ // Set Case light on/off/brightness
+ //
+ #if ENABLED(CASE_LIGHT_MENU)
+ #if DISABLED(CASE_LIGHT_NO_BRIGHTNESS)
+ if (TERN1(CASE_LIGHT_USE_NEOPIXEL, PWM_PIN(CASE_LIGHT_PIN)))
+ SUBMENU(MSG_CASE_LIGHT, menu_case_light);
+ else
+ #endif
+ EDIT_ITEM(bool, MSG_CASE_LIGHT, (bool*)&caselight.on, caselight.update_enabled);
+ #endif
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && LED_CONTROL_MENU
diff --git a/Marlin/src/lcd/menu/menu_main.cpp b/Marlin/src/lcd/menu/menu_main.cpp
new file mode 100644
index 0000000..878ac83
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_main.cpp
@@ -0,0 +1,337 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Main Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_LCD_MENU
+
+#include "menu_item.h"
+#include "../../module/temperature.h"
+#include "../../gcode/queue.h"
+#include "../../module/printcounter.h"
+#include "../../module/stepper.h"
+#include "../../sd/cardreader.h"
+
+#if HAS_GAMES && DISABLED(LCD_INFO_MENU)
+ #include "game/game.h"
+#endif
+
+#if EITHER(SDSUPPORT, HOST_PROMPT_SUPPORT) || defined(ACTION_ON_CANCEL)
+ #define MACHINE_CAN_STOP 1
+#endif
+#if ANY(SDSUPPORT, HOST_PROMPT_SUPPORT, PARK_HEAD_ON_PAUSE) || defined(ACTION_ON_PAUSE)
+ #define MACHINE_CAN_PAUSE 1
+#endif
+
+#if ENABLED(MMU2_MENUS)
+ #include "../../lcd/menu/menu_mmu2.h"
+#endif
+
+#if ENABLED(PASSWORD_FEATURE)
+ #include "../../feature/password/password.h"
+#endif
+
+#if ENABLED(HOST_START_MENU_ITEM) && defined(ACTION_ON_START)
+ #include "../../feature/host_actions.h"
+#endif
+
+#if ENABLED(GCODE_REPEAT_MARKERS)
+ #include "../../feature/repeat.h"
+#endif
+
+void menu_tune();
+void menu_cancelobject();
+void menu_motion();
+void menu_temperature();
+void menu_configuration();
+
+#if ENABLED(CUSTOM_USER_MENUS)
+ void menu_user();
+#endif
+
+#if HAS_POWER_MONITOR
+ void menu_power_monitor();
+#endif
+
+#if ENABLED(MIXING_EXTRUDER)
+ void menu_mixer();
+#endif
+
+#if ENABLED(ADVANCED_PAUSE_FEATURE)
+ void _menu_temp_filament_op(const PauseMode, const int8_t);
+ void menu_change_filament();
+#endif
+
+#if ENABLED(LCD_INFO_MENU)
+ void menu_info();
+#endif
+
+#if EITHER(LED_CONTROL_MENU, CASE_LIGHT_MENU)
+ void menu_led();
+#endif
+
+#if HAS_CUTTER
+ void menu_spindle_laser();
+#endif
+
+#if HAS_MULTI_LANGUAGE
+ void menu_language();
+#endif
+
+void menu_main() {
+ const bool busy = printingIsActive()
+ #if ENABLED(SDSUPPORT)
+ , card_detected = card.isMounted()
+ , card_open = card_detected && card.isFileOpen()
+ #endif
+ ;
+
+ START_MENU();
+ BACK_ITEM(MSG_INFO_SCREEN);
+
+ if (busy) {
+ #if MACHINE_CAN_PAUSE
+ ACTION_ITEM(MSG_PAUSE_PRINT, ui.pause_print);
+ #endif
+ #if MACHINE_CAN_STOP
+ SUBMENU(MSG_STOP_PRINT, []{
+ MenuItem_confirm::select_screen(
+ GET_TEXT(MSG_BUTTON_STOP), GET_TEXT(MSG_BACK),
+ ui.abort_print, ui.goto_previous_screen,
+ GET_TEXT(MSG_STOP_PRINT), (const char *)nullptr, PSTR("?")
+ );
+ });
+ #endif
+
+ #if ENABLED(GCODE_REPEAT_MARKERS)
+ if (repeat.is_active())
+ ACTION_ITEM(MSG_END_LOOPS, repeat.cancel);
+ #endif
+
+ SUBMENU(MSG_TUNE, menu_tune);
+
+ #if ENABLED(CANCEL_OBJECTS) && DISABLED(SLIM_LCD_MENUS)
+ SUBMENU(MSG_CANCEL_OBJECT, []{ editable.int8 = -1; ui.goto_screen(menu_cancelobject); });
+ #endif
+ }
+ else {
+
+ #if !HAS_ENCODER_WHEEL && ENABLED(SDSUPPORT)
+
+ // *** IF THIS SECTION IS CHANGED, REPRODUCE BELOW ***
+
+ //
+ // Run Auto Files
+ //
+ #if ENABLED(MENU_ADDAUTOSTART)
+ ACTION_ITEM(MSG_RUN_AUTO_FILES, card.autofile_begin);
+ #endif
+
+ if (card_detected) {
+ if (!card_open) {
+ SUBMENU(MSG_MEDIA_MENU, MEDIA_MENU_GATEWAY);
+ #if PIN_EXISTS(SD_DETECT)
+ GCODES_ITEM(MSG_CHANGE_MEDIA, PSTR("M21"));
+ #else
+ GCODES_ITEM(MSG_RELEASE_MEDIA, PSTR("M22"));
+ #endif
+ }
+ }
+ else {
+ #if PIN_EXISTS(SD_DETECT)
+ ACTION_ITEM(MSG_NO_MEDIA, nullptr);
+ #else
+ GCODES_ITEM(MSG_ATTACH_MEDIA, PSTR("M21"));
+ #endif
+ }
+
+ #endif // !HAS_ENCODER_WHEEL && SDSUPPORT
+
+ if (TERN0(MACHINE_CAN_PAUSE, printingIsPaused()))
+ ACTION_ITEM(MSG_RESUME_PRINT, ui.resume_print);
+
+ #if ENABLED(HOST_START_MENU_ITEM) && defined(ACTION_ON_START)
+ ACTION_ITEM(MSG_HOST_START_PRINT, host_action_start);
+ #endif
+
+ SUBMENU(MSG_MOTION, menu_motion);
+ }
+
+ #if HAS_CUTTER
+ SUBMENU(MSG_CUTTER(MENU), STICKY_SCREEN(menu_spindle_laser));
+ #endif
+
+ #if HAS_TEMPERATURE
+ SUBMENU(MSG_TEMPERATURE, menu_temperature);
+ #endif
+
+ #if HAS_POWER_MONITOR
+ SUBMENU(MSG_POWER_MONITOR, menu_power_monitor);
+ #endif
+
+ #if ENABLED(MIXING_EXTRUDER)
+ SUBMENU(MSG_MIXER, menu_mixer);
+ #endif
+
+ #if ENABLED(MMU2_MENUS)
+ if (!busy) SUBMENU(MSG_MMU2_MENU, menu_mmu2);
+ #endif
+
+ SUBMENU(MSG_CONFIGURATION, menu_configuration);
+
+ #if ENABLED(CUSTOM_USER_MENUS)
+ #ifdef CUSTOM_USER_MENU_TITLE
+ SUBMENU_P(PSTR(CUSTOM_USER_MENU_TITLE), menu_user);
+ #else
+ SUBMENU(MSG_USER_MENU, menu_user);
+ #endif
+ #endif
+
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ #if E_STEPPERS == 1 && DISABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ if (thermalManager.targetHotEnoughToExtrude(active_extruder))
+ GCODES_ITEM(MSG_FILAMENTCHANGE, PSTR("M600 B0"));
+ else
+ SUBMENU(MSG_FILAMENTCHANGE, []{ _menu_temp_filament_op(PAUSE_MODE_CHANGE_FILAMENT, 0); });
+ #else
+ SUBMENU(MSG_FILAMENTCHANGE, menu_change_filament);
+ #endif
+ #endif
+
+ #if ENABLED(LCD_INFO_MENU)
+ SUBMENU(MSG_INFO_MENU, menu_info);
+ #endif
+
+ #if EITHER(LED_CONTROL_MENU, CASE_LIGHT_MENU)
+ SUBMENU(MSG_LEDS, menu_led);
+ #endif
+
+ //
+ // Switch power on/off
+ //
+ #if ENABLED(PSU_CONTROL)
+ if (powersupply_on)
+ GCODES_ITEM(MSG_SWITCH_PS_OFF, PSTR("M81"));
+ else
+ GCODES_ITEM(MSG_SWITCH_PS_ON, PSTR("M80"));
+ #endif
+
+ #if BOTH(HAS_ENCODER_WHEEL, SDSUPPORT)
+
+ if (!busy) {
+
+ // *** IF THIS SECTION IS CHANGED, REPRODUCE ABOVE ***
+
+ //
+ // Autostart
+ //
+ #if ENABLED(MENU_ADDAUTOSTART)
+ ACTION_ITEM(MSG_RUN_AUTO_FILES, card.autofile_begin);
+ #endif
+
+ if (card_detected) {
+ if (!card_open) {
+ #if PIN_EXISTS(SD_DETECT)
+ GCODES_ITEM(MSG_CHANGE_MEDIA, PSTR("M21"));
+ #else
+ GCODES_ITEM(MSG_RELEASE_MEDIA, PSTR("M22"));
+ #endif
+ SUBMENU(MSG_MEDIA_MENU, MEDIA_MENU_GATEWAY);
+ }
+ }
+ else {
+ #if PIN_EXISTS(SD_DETECT)
+ ACTION_ITEM(MSG_NO_MEDIA, nullptr);
+ #else
+ GCODES_ITEM(MSG_ATTACH_MEDIA, PSTR("M21"));
+ #endif
+ }
+ }
+
+ #endif // HAS_ENCODER_WHEEL && SDSUPPORT
+
+ #if HAS_SERVICE_INTERVALS
+ static auto _service_reset = [](const int index) {
+ print_job_timer.resetServiceInterval(index);
+ ui.completion_feedback();
+ ui.reset_status();
+ ui.return_to_status();
+ };
+ #if SERVICE_INTERVAL_1 > 0
+ CONFIRM_ITEM_P(PSTR(SERVICE_NAME_1),
+ MSG_BUTTON_RESET, MSG_BUTTON_CANCEL,
+ []{ _service_reset(1); }, ui.goto_previous_screen,
+ GET_TEXT(MSG_SERVICE_RESET), F(SERVICE_NAME_1), PSTR("?")
+ );
+ #endif
+ #if SERVICE_INTERVAL_2 > 0
+ CONFIRM_ITEM_P(PSTR(SERVICE_NAME_2),
+ MSG_BUTTON_RESET, MSG_BUTTON_CANCEL,
+ []{ _service_reset(2); }, ui.goto_previous_screen,
+ GET_TEXT(MSG_SERVICE_RESET), F(SERVICE_NAME_2), PSTR("?")
+ );
+ #endif
+ #if SERVICE_INTERVAL_3 > 0
+ CONFIRM_ITEM_P(PSTR(SERVICE_NAME_3),
+ MSG_BUTTON_RESET, MSG_BUTTON_CANCEL,
+ []{ _service_reset(3); }, ui.goto_previous_screen,
+ GET_TEXT(MSG_SERVICE_RESET), F(SERVICE_NAME_3), PSTR("?")
+ );
+ #endif
+ #endif
+
+ #if HAS_GAMES && DISABLED(LCD_INFO_MENU)
+ #if ENABLED(GAMES_EASTER_EGG)
+ SKIP_ITEM();
+ SKIP_ITEM();
+ SKIP_ITEM();
+ #endif
+ // Game sub-menu or the individual game
+ {
+ SUBMENU(
+ #if HAS_GAME_MENU
+ MSG_GAMES, menu_game
+ #elif ENABLED(MARLIN_BRICKOUT)
+ MSG_BRICKOUT, brickout.enter_game
+ #elif ENABLED(MARLIN_INVADERS)
+ MSG_INVADERS, invaders.enter_game
+ #elif ENABLED(MARLIN_SNAKE)
+ MSG_SNAKE, snake.enter_game
+ #elif ENABLED(MARLIN_MAZE)
+ MSG_MAZE, maze.enter_game
+ #endif
+ );
+ }
+ #endif
+
+ #if HAS_MULTI_LANGUAGE
+ SUBMENU(LANGUAGE, menu_language);
+ #endif
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU
diff --git a/Marlin/src/lcd/menu/menu_media.cpp b/Marlin/src/lcd/menu/menu_media.cpp
new file mode 100644
index 0000000..7a525d0
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_media.cpp
@@ -0,0 +1,141 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// SD Card Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, SDSUPPORT)
+
+#include "menu_item.h"
+#include "../../sd/cardreader.h"
+
+void lcd_sd_updir() {
+ ui.encoderPosition = card.cdup() ? ENCODER_STEPS_PER_MENU_ITEM : 0;
+ encoderTopLine = 0;
+ ui.screen_changed = true;
+ ui.refresh();
+}
+
+#if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
+
+ uint16_t sd_encoder_position = 0xFFFF;
+ int8_t sd_top_line, sd_items;
+
+ void MarlinUI::reselect_last_file() {
+ if (sd_encoder_position == 0xFFFF) return;
+ goto_screen(menu_media, sd_encoder_position, sd_top_line, sd_items);
+ sd_encoder_position = 0xFFFF;
+ defer_status_screen();
+ }
+
+#endif
+
+inline void sdcard_start_selected_file() {
+ card.openAndPrintFile(card.filename);
+ ui.return_to_status();
+ ui.reset_status();
+}
+
+class MenuItem_sdfile : public MenuItem_sdbase {
+ public:
+ static inline void draw(const bool sel, const uint8_t row, PGM_P const pstr, CardReader &theCard) {
+ MenuItem_sdbase::draw(sel, row, pstr, theCard, false);
+ }
+ static void action(PGM_P const pstr, CardReader &) {
+ #if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
+ // Save menu state for the selected file
+ sd_encoder_position = ui.encoderPosition;
+ sd_top_line = encoderTopLine;
+ sd_items = screen_items;
+ #endif
+ #if ENABLED(SD_MENU_CONFIRM_START)
+ MenuItem_submenu::action(pstr, []{
+ char * const longest = card.longest_filename();
+ char buffer[strlen(longest) + 2];
+ buffer[0] = ' ';
+ strcpy(buffer + 1, longest);
+ MenuItem_confirm::select_screen(
+ GET_TEXT(MSG_BUTTON_PRINT), GET_TEXT(MSG_BUTTON_CANCEL),
+ sdcard_start_selected_file, ui.goto_previous_screen,
+ GET_TEXT(MSG_START_PRINT), buffer, PSTR("?")
+ );
+ });
+ #else
+ sdcard_start_selected_file();
+ UNUSED(pstr);
+ #endif
+ }
+};
+
+class MenuItem_sdfolder : public MenuItem_sdbase {
+ public:
+ static inline void draw(const bool sel, const uint8_t row, PGM_P const pstr, CardReader &theCard) {
+ MenuItem_sdbase::draw(sel, row, pstr, theCard, true);
+ }
+ static void action(PGM_P const, CardReader &theCard) {
+ card.cd(theCard.filename);
+ encoderTopLine = 0;
+ ui.encoderPosition = 2 * (ENCODER_STEPS_PER_MENU_ITEM);
+ ui.screen_changed = true;
+ TERN_(HAS_MARLINUI_U8GLIB, ui.drawing_screen = false);
+ ui.refresh();
+ }
+};
+
+void menu_media() {
+ ui.encoder_direction_menus();
+
+ #if HAS_MARLINUI_U8GLIB
+ static uint16_t fileCnt;
+ if (ui.first_page) fileCnt = card.get_num_Files();
+ #else
+ const uint16_t fileCnt = card.get_num_Files();
+ #endif
+
+ START_MENU();
+ BACK_ITEM_P(TERN1(BROWSE_MEDIA_ON_INSERT, screen_history_depth) ? GET_TEXT(MSG_MAIN) : GET_TEXT(MSG_BACK));
+ if (card.flag.workDirIsRoot) {
+ #if !PIN_EXISTS(SD_DETECT)
+ ACTION_ITEM(MSG_REFRESH, []{ encoderTopLine = 0; card.mount(); });
+ #endif
+ }
+ else if (card.isMounted())
+ ACTION_ITEM_P(PSTR(LCD_STR_FOLDER ".."), lcd_sd_updir);
+
+ if (ui.should_draw()) for (uint16_t i = 0; i < fileCnt; i++) {
+ if (_menuLineNr == _thisItemNr) {
+ card.getfilename_sorted(SD_ORDER(i, fileCnt));
+ if (card.flag.filenameIsDir)
+ MENU_ITEM(sdfolder, MSG_MEDIA_MENU, card);
+ else
+ MENU_ITEM(sdfile, MSG_MEDIA_MENU, card);
+ }
+ else
+ SKIP_ITEM();
+ }
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && SDSUPPORT
diff --git a/Marlin/src/lcd/menu/menu_mixer.cpp b/Marlin/src/lcd/menu/menu_mixer.cpp
new file mode 100644
index 0000000..d07b89c
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_mixer.cpp
@@ -0,0 +1,278 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Mixer Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, MIXING_EXTRUDER)
+
+#include "menu_item.h"
+#include "menu_addon.h"
+
+#include "../../feature/mixing.h"
+
+#if HAS_GRAPHICAL_TFT
+ #include "../tft/tft.h"
+#endif
+
+#define CHANNEL_MIX_EDITING !HAS_DUAL_MIXING
+
+#if ENABLED(GRADIENT_MIX)
+
+ void _lcd_mixer_gradient_z_edit(const bool isend) {
+ ui.defer_status_screen();
+ ENCODER_RATE_MULTIPLY(true);
+
+ float &zvar = isend ? mixer.gradient.end_z : mixer.gradient.start_z;
+
+ if (ui.encoderPosition) {
+ zvar += float(int32_t(ui.encoderPosition)) * 0.1;
+ ui.encoderPosition = 0;
+ NOLESS(zvar, 0);
+ NOMORE(zvar, Z_MAX_POS);
+ }
+
+ if (ui.should_draw()) {
+ char tmp[16];
+ SETCURSOR(1, (LCD_HEIGHT - 1) / 2);
+ lcd_put_u8str_P(isend ? GET_TEXT(MSG_END_Z) : GET_TEXT(MSG_START_Z));
+ sprintf_P(tmp, PSTR("%4d.%d mm"), int(zvar), int(zvar * 10) % 10);
+ SETCURSOR_RJ(9, (LCD_HEIGHT - 1) / 2);
+ lcd_put_u8str(tmp);
+ }
+
+ if (ui.lcd_clicked) {
+ if (isend && zvar < mixer.gradient.start_z)
+ mixer.gradient.start_z = zvar;
+ else if (!isend && zvar > mixer.gradient.end_z)
+ mixer.gradient.end_z = zvar;
+ mixer.refresh_gradient();
+ ui.goto_previous_screen();
+ }
+ else {
+ TERN_(HAS_GRAPHICAL_TFT, tft.draw_edit_screen_buttons());
+ }
+ }
+
+ void lcd_mixer_edit_gradient_menu() {
+ START_MENU();
+ BACK_ITEM(MSG_MIXER);
+
+ EDIT_ITEM(int8, MSG_START_VTOOL, &mixer.gradient.start_vtool, 0, MIXING_VIRTUAL_TOOLS - 1, mixer.refresh_gradient);
+ EDIT_ITEM(int8, MSG_END_VTOOL, &mixer.gradient.end_vtool, 0, MIXING_VIRTUAL_TOOLS - 1, mixer.refresh_gradient);
+
+ #if ENABLED(GRADIENT_VTOOL)
+ EDIT_ITEM(int8, MSG_GRADIENT_ALIAS, &mixer.gradient.vtool_index, -1, MIXING_VIRTUAL_TOOLS - 1, mixer.refresh_gradient);
+ #endif
+
+ char tmp[18];
+
+ PGM_P const slabel = GET_TEXT(MSG_START_Z);
+ SUBMENU_P(slabel, []{ _lcd_mixer_gradient_z_edit(false); });
+ MENU_ITEM_ADDON_START_RJ(11);
+ sprintf_P(tmp, PSTR("%4d.%d mm"), int(mixer.gradient.start_z), int(mixer.gradient.start_z * 10) % 10);
+ lcd_put_u8str(tmp);
+ MENU_ITEM_ADDON_END();
+
+ PGM_P const elabel = GET_TEXT(MSG_END_Z);
+ SUBMENU_P(elabel, []{ _lcd_mixer_gradient_z_edit(true); });
+ MENU_ITEM_ADDON_START_RJ(11);
+ sprintf_P(tmp, PSTR("%4d.%d mm"), int(mixer.gradient.end_z), int(mixer.gradient.end_z * 10) % 10);
+ lcd_put_u8str(tmp);
+ MENU_ITEM_ADDON_END();
+
+ END_MENU();
+ }
+
+#endif // GRADIENT_MIX
+
+static uint8_t v_index;
+
+#if HAS_DUAL_MIXING
+ void _lcd_draw_mix(const uint8_t y) {
+ char tmp[20]; // "100%_100%"
+ sprintf_P(tmp, PSTR("%3d%% %3d%%"), int(mixer.mix[0]), int(mixer.mix[1]));
+ SETCURSOR(2, y); lcd_put_u8str_P(GET_TEXT(MSG_MIX));
+ SETCURSOR_RJ(10, y); lcd_put_u8str(tmp);
+ }
+#endif
+
+void _lcd_mixer_select_vtool() {
+ mixer.T(v_index);
+ TERN_(HAS_DUAL_MIXING, _lcd_draw_mix(LCD_HEIGHT - 1));
+}
+
+#if CHANNEL_MIX_EDITING
+
+ void _lcd_mixer_cycle_mix() {
+ uint16_t bits = 0;
+ MIXER_STEPPER_LOOP(i) if (mixer.collector[i]) SBI(bits, i);
+ bits = (bits + 1) & (_BV(MIXING_STEPPERS) - 1);
+ if (!bits) ++bits;
+ MIXER_STEPPER_LOOP(i) mixer.collector[i] = TEST(bits, i) ? 10.0f : 0.0f;
+ ui.refresh();
+ }
+
+ void _lcd_mixer_commit_vtool() {
+ mixer.normalize();
+ ui.goto_previous_screen();
+ }
+
+#endif
+
+void lcd_mixer_mix_edit() {
+
+ #if HAS_DUAL_MIXING && !CHANNEL_MIX_EDITING
+
+ // Adjust 2-channel mix from the encoder
+ if (ui.encoderPosition) {
+ mixer.mix[0] += int32_t(ui.encoderPosition);
+ ui.encoderPosition = 0;
+ if (mixer.mix[0] < 0) mixer.mix[0] += 101;
+ if (mixer.mix[0] > 100) mixer.mix[0] -= 101;
+ mixer.mix[1] = 100 - mixer.mix[0];
+ }
+ _lcd_draw_mix((LCD_HEIGHT - 1) / 2);
+
+ // Click to commit the change
+ if (ui.lcd_clicked) {
+ mixer.update_vtool_from_mix();
+ ui.goto_previous_screen();
+ }
+
+ TERN_(HAS_GRAPHICAL_TFT, tft.draw_edit_screen_buttons());
+
+ #else
+
+ START_MENU();
+ BACK_ITEM(MSG_MIXER);
+
+ #if CHANNEL_MIX_EDITING
+
+ LOOP_S_LE_N(n, 1, MIXING_STEPPERS)
+ EDIT_ITEM_FAST_N(float42_52, n, MSG_MIX_COMPONENT_N, &mixer.collector[n-1], 0, 10);
+
+ ACTION_ITEM(MSG_CYCLE_MIX, _lcd_mixer_cycle_mix);
+ ACTION_ITEM(MSG_COMMIT_VTOOL, _lcd_mixer_commit_vtool);
+
+ #endif
+
+ END_MENU();
+
+ #endif
+}
+
+#if HAS_DUAL_MIXING
+
+ //
+ // Toggle Dual-Mix
+ //
+ inline void _lcd_mixer_toggle_mix() {
+ mixer.mix[0] = mixer.mix[0] == 100 ? 0 : 100;
+ mixer.mix[1] = 100 - mixer.mix[0];
+ mixer.update_vtool_from_mix();
+ ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
+ }
+
+#endif
+
+#if CHANNEL_MIX_EDITING
+
+ //
+ // Prepare and Edit Mix
+ //
+ inline void _lcd_goto_mix_edit() {
+ mixer.refresh_collector(10, v_index);
+ ui.goto_screen(lcd_mixer_mix_edit);
+ lcd_mixer_mix_edit();
+ }
+
+#endif
+
+#if ENABLED(GRADIENT_MIX)
+ //
+ // Reverse Gradient
+ //
+ inline void _lcd_mixer_reverse_gradient() {
+ const uint8_t sv = mixer.gradient.start_vtool;
+ mixer.gradient.start_vtool = mixer.gradient.end_vtool;
+ mixer.gradient.end_vtool = sv;
+ mixer.refresh_gradient();
+ ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
+ }
+#endif
+
+void menu_mixer() {
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ v_index = mixer.get_current_vtool();
+ EDIT_ITEM(uint8, MSG_ACTIVE_VTOOL, &v_index, 0, MIXING_VIRTUAL_TOOLS - 1, _lcd_mixer_select_vtool, ENABLED(HAS_DUAL_MIXING));
+
+ #if HAS_DUAL_MIXING
+ {
+ char tmp[11];
+ SUBMENU(MSG_MIX, lcd_mixer_mix_edit);
+ MENU_ITEM_ADDON_START_RJ(9);
+ mixer.update_mix_from_vtool();
+ sprintf_P(tmp, PSTR("%3d;%3d%%"), int(mixer.mix[0]), int(mixer.mix[1]));
+ lcd_put_u8str(tmp);
+ MENU_ITEM_ADDON_END();
+ ACTION_ITEM(MSG_TOGGLE_MIX, _lcd_mixer_toggle_mix);
+ }
+ #else
+ SUBMENU(MSG_MIX, _lcd_goto_mix_edit);
+ #endif
+
+ //
+ // Reset All V-Tools
+ //
+ CONFIRM_ITEM(MSG_RESET_VTOOLS,
+ MSG_BUTTON_RESET, MSG_BUTTON_CANCEL,
+ []{
+ mixer.reset_vtools();
+ LCD_MESSAGEPGM(MSG_VTOOLS_RESET);
+ ui.return_to_status();
+ },
+ nullptr,
+ GET_TEXT(MSG_RESET_VTOOLS), (const char *)nullptr, PSTR("?")
+ );
+
+ #if ENABLED(GRADIENT_MIX)
+ {
+ char tmp[13];
+ SUBMENU(MSG_GRADIENT, lcd_mixer_edit_gradient_menu);
+ MENU_ITEM_ADDON_START_RJ(9);
+ sprintf_P(tmp, PSTR("T%i->T%i"), mixer.gradient.start_vtool, mixer.gradient.end_vtool);
+ lcd_put_u8str(tmp);
+ MENU_ITEM_ADDON_END();
+ ACTION_ITEM(MSG_REVERSE_GRADIENT, _lcd_mixer_reverse_gradient);
+ }
+ #endif
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && MIXING_EXTRUDER
diff --git a/Marlin/src/lcd/menu/menu_mmu2.cpp b/Marlin/src/lcd/menu/menu_mmu2.cpp
new file mode 100644
index 0000000..7e71f00
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_mmu2.cpp
@@ -0,0 +1,170 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if BOTH(HAS_LCD_MENU, MMU2_MENUS)
+
+#include "../../feature/mmu/mmu2.h"
+#include "menu_mmu2.h"
+#include "menu_item.h"
+
+//
+// Load Filament
+//
+
+inline void action_mmu2_load_filament_to_nozzle(const uint8_t tool) {
+ ui.reset_status();
+ ui.return_to_status();
+ ui.status_printf_P(0, GET_TEXT(MSG_MMU2_LOADING_FILAMENT), int(tool + 1));
+ if (mmu2.load_filament_to_nozzle(tool))
+ ui.reset_status();
+ ui.return_to_status();
+}
+
+void _mmu2_load_filament(uint8_t index) {
+ ui.return_to_status();
+ ui.status_printf_P(0, GET_TEXT(MSG_MMU2_LOADING_FILAMENT), int(index + 1));
+ mmu2.load_filament(index);
+ ui.reset_status();
+}
+void action_mmu2_load_all() {
+ LOOP_L_N(i, EXTRUDERS) _mmu2_load_filament(i);
+ ui.return_to_status();
+}
+
+void menu_mmu2_load_filament() {
+ START_MENU();
+ BACK_ITEM(MSG_MMU2_MENU);
+ ACTION_ITEM(MSG_MMU2_ALL, action_mmu2_load_all);
+ LOOP_L_N(i, EXTRUDERS) ACTION_ITEM_N(i, MSG_MMU2_FILAMENT_N, []{ _mmu2_load_filament(MenuItemBase::itemIndex); });
+ END_MENU();
+}
+
+void menu_mmu2_load_to_nozzle() {
+ START_MENU();
+ BACK_ITEM(MSG_MMU2_MENU);
+ LOOP_L_N(i, EXTRUDERS) ACTION_ITEM_N(i, MSG_MMU2_FILAMENT_N, []{ action_mmu2_load_filament_to_nozzle(MenuItemBase::itemIndex); });
+ END_MENU();
+}
+
+//
+// Eject Filament
+//
+
+void _mmu2_eject_filament(uint8_t index) {
+ ui.reset_status();
+ ui.return_to_status();
+ ui.status_printf_P(0, GET_TEXT(MSG_MMU2_EJECTING_FILAMENT), int(index + 1));
+ if (mmu2.eject_filament(index, true)) ui.reset_status();
+}
+
+void action_mmu2_unload_filament() {
+ ui.reset_status();
+ ui.return_to_status();
+ LCD_MESSAGEPGM(MSG_MMU2_UNLOADING_FILAMENT);
+ idle();
+ if (mmu2.unload()) ui.reset_status();
+}
+
+void menu_mmu2_eject_filament() {
+ START_MENU();
+ BACK_ITEM(MSG_MMU2_MENU);
+ LOOP_L_N(i, EXTRUDERS) ACTION_ITEM_N(i, MSG_MMU2_FILAMENT_N, []{ _mmu2_eject_filament(MenuItemBase::itemIndex); });
+ END_MENU();
+}
+
+//
+// MMU2 Menu
+//
+
+void action_mmu2_reset() {
+ mmu2.init();
+ ui.reset_status();
+}
+
+void menu_mmu2() {
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+ SUBMENU(MSG_MMU2_LOAD_FILAMENT, menu_mmu2_load_filament);
+ SUBMENU(MSG_MMU2_LOAD_TO_NOZZLE, menu_mmu2_load_to_nozzle);
+ SUBMENU(MSG_MMU2_EJECT_FILAMENT, menu_mmu2_eject_filament);
+ ACTION_ITEM(MSG_MMU2_UNLOAD_FILAMENT, action_mmu2_unload_filament);
+ ACTION_ITEM(MSG_MMU2_RESET, action_mmu2_reset);
+ END_MENU();
+}
+
+//
+// T* Choose Filament
+//
+
+uint8_t feeder_index;
+bool wait_for_mmu_menu;
+
+inline void action_mmu2_chosen(const uint8_t index) {
+ feeder_index = index;
+ wait_for_mmu_menu = false;
+}
+
+void menu_mmu2_choose_filament() {
+ START_MENU();
+ #if LCD_HEIGHT > 2
+ STATIC_ITEM(MSG_MMU2_CHOOSE_FILAMENT_HEADER, SS_DEFAULT|SS_INVERT);
+ #endif
+ LOOP_L_N(i, EXTRUDERS) ACTION_ITEM_N(i, MSG_MMU2_FILAMENT_N, []{ action_mmu2_chosen(MenuItemBase::itemIndex); });
+ END_MENU();
+}
+
+//
+// MMU2 Filament Runout
+//
+
+void menu_mmu2_pause() {
+ feeder_index = mmu2.get_current_tool();
+ START_MENU();
+ #if LCD_HEIGHT > 2
+ STATIC_ITEM(MSG_FILAMENT_CHANGE_HEADER, SS_DEFAULT|SS_INVERT);
+ #endif
+ ACTION_ITEM(MSG_MMU2_RESUME, []{ wait_for_mmu_menu = false; });
+ ACTION_ITEM(MSG_MMU2_UNLOAD_FILAMENT, []{ mmu2.unload(); });
+ ACTION_ITEM(MSG_MMU2_LOAD_FILAMENT, []{ mmu2.load_filament(feeder_index); });
+ ACTION_ITEM(MSG_MMU2_LOAD_TO_NOZZLE, []{ mmu2.load_filament_to_nozzle(feeder_index); });
+ END_MENU();
+}
+
+void mmu2_M600() {
+ ui.defer_status_screen();
+ ui.goto_screen(menu_mmu2_pause);
+ wait_for_mmu_menu = true;
+ while (wait_for_mmu_menu) idle();
+}
+
+uint8_t mmu2_choose_filament() {
+ ui.defer_status_screen();
+ ui.goto_screen(menu_mmu2_choose_filament);
+ wait_for_mmu_menu = true;
+ while (wait_for_mmu_menu) idle();
+ ui.return_to_status();
+ return feeder_index;
+}
+
+#endif // HAS_LCD_MENU && MMU2_MENUS
diff --git a/Marlin/src/lcd/menu/menu_mmu2.h b/Marlin/src/lcd/menu/menu_mmu2.h
new file mode 100644
index 0000000..4230c01
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_mmu2.h
@@ -0,0 +1,28 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include <stdint.h>
+
+void menu_mmu2();
+void mmu2_M600();
+uint8_t mmu2_choose_filament();
diff --git a/Marlin/src/lcd/menu/menu_motion.cpp b/Marlin/src/lcd/menu/menu_motion.cpp
new file mode 100644
index 0000000..71fc424
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_motion.cpp
@@ -0,0 +1,415 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Motion Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_LCD_MENU
+
+#include "menu_item.h"
+#include "menu_addon.h"
+
+#include "../../module/motion.h"
+#include "../../gcode/parser.h" // for inch support
+
+#if ENABLED(DELTA)
+ #include "../../module/delta.h"
+#endif
+
+#if ENABLED(PREVENT_COLD_EXTRUSION)
+ #include "../../module/temperature.h"
+#endif
+
+#if HAS_LEVELING
+ #include "../../module/planner.h"
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+#if ENABLED(MANUAL_E_MOVES_RELATIVE)
+ float manual_move_e_origin = 0;
+#endif
+
+//
+// "Motion" > "Move Axis" submenu
+//
+
+static void _lcd_move_xyz(PGM_P const name, const AxisEnum axis) {
+ if (ui.use_click()) return ui.goto_previous_screen_no_defer();
+ if (ui.encoderPosition && !ui.manual_move.processing) {
+ // Get motion limit from software endstops, if any
+ float min, max;
+ soft_endstop.get_manual_axis_limits(axis, min, max);
+
+ // Delta limits XY based on the current offset from center
+ // This assumes the center is 0,0
+ #if ENABLED(DELTA)
+ if (axis != Z_AXIS) {
+ max = SQRT(sq((float)(DELTA_PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis
+ min = -max;
+ }
+ #endif
+
+ // Get the new position
+ const float diff = float(int32_t(ui.encoderPosition)) * ui.manual_move.menu_scale;
+ #if IS_KINEMATIC
+ ui.manual_move.offset += diff;
+ if (int32_t(ui.encoderPosition) < 0)
+ NOLESS(ui.manual_move.offset, min - current_position[axis]);
+ else
+ NOMORE(ui.manual_move.offset, max - current_position[axis]);
+ #else
+ current_position[axis] += diff;
+ if (int32_t(ui.encoderPosition) < 0)
+ NOLESS(current_position[axis], min);
+ else
+ NOMORE(current_position[axis], max);
+ #endif
+
+ ui.manual_move.soon(axis);
+ ui.refresh(LCDVIEW_REDRAW_NOW);
+ }
+ ui.encoderPosition = 0;
+ if (ui.should_draw()) {
+ const float pos = NATIVE_TO_LOGICAL(
+ ui.manual_move.processing ? destination[axis] : current_position[axis] + TERN0(IS_KINEMATIC, ui.manual_move.offset),
+ axis
+ );
+ if (parser.using_inch_units()) {
+ const float imp_pos = LINEAR_UNIT(pos);
+ MenuEditItemBase::draw_edit_screen(name, ftostr63(imp_pos));
+ }
+ else
+ MenuEditItemBase::draw_edit_screen(name, ui.manual_move.menu_scale >= 0.1f ? ftostr41sign(pos) : ftostr63(pos));
+ }
+}
+void lcd_move_x() { _lcd_move_xyz(GET_TEXT(MSG_MOVE_X), X_AXIS); }
+void lcd_move_y() { _lcd_move_xyz(GET_TEXT(MSG_MOVE_Y), Y_AXIS); }
+void lcd_move_z() { _lcd_move_xyz(GET_TEXT(MSG_MOVE_Z), Z_AXIS); }
+
+#if E_MANUAL
+
+ static void lcd_move_e(TERN_(MULTI_MANUAL, const int8_t eindex=-1)) {
+ if (ui.use_click()) return ui.goto_previous_screen_no_defer();
+ if (ui.encoderPosition) {
+ if (!ui.manual_move.processing) {
+ const float diff = float(int32_t(ui.encoderPosition)) * ui.manual_move.menu_scale;
+ TERN(IS_KINEMATIC, ui.manual_move.offset, current_position.e) += diff;
+ ui.manual_move.soon(E_AXIS
+ #if MULTI_MANUAL
+ , eindex
+ #endif
+ );
+ ui.refresh(LCDVIEW_REDRAW_NOW);
+ }
+ ui.encoderPosition = 0;
+ }
+ if (ui.should_draw()) {
+ TERN_(MULTI_MANUAL, MenuItemBase::init(eindex));
+ MenuEditItemBase::draw_edit_screen(
+ GET_TEXT(TERN(MULTI_MANUAL, MSG_MOVE_EN, MSG_MOVE_E)),
+ ftostr41sign(current_position.e
+ + TERN0(IS_KINEMATIC, ui.manual_move.offset)
+ - TERN0(MANUAL_E_MOVES_RELATIVE, manual_move_e_origin)
+ )
+ );
+ } // should_draw
+ }
+
+#endif // E_MANUAL
+
+//
+// "Motion" > "Move Xmm" > "Move XYZ" submenu
+//
+
+#ifndef FINE_MANUAL_MOVE
+ #define FINE_MANUAL_MOVE 0.025
+#endif
+
+screenFunc_t _manual_move_func_ptr;
+
+void _goto_manual_move(const float scale) {
+ ui.defer_status_screen();
+ ui.manual_move.menu_scale = scale;
+ ui.goto_screen(_manual_move_func_ptr);
+}
+
+void _menu_move_distance(const AxisEnum axis, const screenFunc_t func, const int8_t eindex=-1) {
+ _manual_move_func_ptr = func;
+ START_MENU();
+ if (LCD_HEIGHT >= 4) {
+ switch (axis) {
+ case X_AXIS: STATIC_ITEM(MSG_MOVE_X, SS_DEFAULT|SS_INVERT); break;
+ case Y_AXIS: STATIC_ITEM(MSG_MOVE_Y, SS_DEFAULT|SS_INVERT); break;
+ case Z_AXIS: STATIC_ITEM(MSG_MOVE_Z, SS_DEFAULT|SS_INVERT); break;
+ default:
+ TERN_(MANUAL_E_MOVES_RELATIVE, manual_move_e_origin = current_position.e);
+ STATIC_ITEM(MSG_MOVE_E, SS_DEFAULT|SS_INVERT);
+ break;
+ }
+ }
+
+ BACK_ITEM(MSG_MOVE_AXIS);
+ if (parser.using_inch_units()) {
+ SUBMENU(MSG_MOVE_01IN, []{ _goto_manual_move(IN_TO_MM(0.100f)); });
+ SUBMENU(MSG_MOVE_001IN, []{ _goto_manual_move(IN_TO_MM(0.010f)); });
+ SUBMENU(MSG_MOVE_0001IN, []{ _goto_manual_move(IN_TO_MM(0.001f)); });
+ }
+ else {
+ SUBMENU(MSG_MOVE_10MM, []{ _goto_manual_move(10); });
+ SUBMENU(MSG_MOVE_1MM, []{ _goto_manual_move( 1); });
+ SUBMENU(MSG_MOVE_01MM, []{ _goto_manual_move( 0.1f); });
+ if (axis == Z_AXIS && (FINE_MANUAL_MOVE) > 0.0f && (FINE_MANUAL_MOVE) < 0.1f) {
+ // Determine digits needed right of decimal
+ constexpr uint8_t digs = !UNEAR_ZERO((FINE_MANUAL_MOVE) * 1000 - int((FINE_MANUAL_MOVE) * 1000)) ? 4 :
+ !UNEAR_ZERO((FINE_MANUAL_MOVE) * 100 - int((FINE_MANUAL_MOVE) * 100)) ? 3 : 2;
+ PGM_P const label = GET_TEXT(MSG_MOVE_N_MM);
+ char tmp[strlen_P(label) + 10 + 1], numstr[10];
+ sprintf_P(tmp, label, dtostrf(FINE_MANUAL_MOVE, 1, digs, numstr));
+
+ #if DISABLED(HAS_GRAPHICAL_TFT)
+ SUBMENU_P(NUL_STR, []{ _goto_manual_move(float(FINE_MANUAL_MOVE)); });
+ MENU_ITEM_ADDON_START(0 + ENABLED(HAS_MARLINUI_HD44780));
+ lcd_put_u8str(tmp);
+ MENU_ITEM_ADDON_END();
+ #else
+ SUBMENU_P(tmp, []{ _goto_manual_move(float(FINE_MANUAL_MOVE)); });
+ #endif
+ }
+ }
+ END_MENU();
+}
+
+#if E_MANUAL
+
+ inline void _goto_menu_move_distance_e() {
+ ui.goto_screen([]{ _menu_move_distance(E_AXIS, []{ lcd_move_e(TERN_(MULTI_MANUAL, active_extruder)); }, -1); });
+ }
+
+ inline void _menu_move_distance_e_maybe() {
+ #if ENABLED(PREVENT_COLD_EXTRUSION)
+ const bool too_cold = thermalManager.tooColdToExtrude(active_extruder);
+ if (too_cold) {
+ ui.goto_screen([]{
+ MenuItem_confirm::select_screen(
+ GET_TEXT(MSG_BUTTON_PROCEED), GET_TEXT(MSG_BACK),
+ _goto_menu_move_distance_e, ui.goto_previous_screen,
+ GET_TEXT(MSG_HOTEND_TOO_COLD), (const char *)nullptr, PSTR("!")
+ );
+ });
+ return;
+ }
+ #endif
+ _goto_menu_move_distance_e();
+ }
+
+#endif // E_MANUAL
+
+void menu_move() {
+ START_MENU();
+ BACK_ITEM(MSG_MOTION);
+
+ #if BOTH(HAS_SOFTWARE_ENDSTOPS, SOFT_ENDSTOPS_MENU_ITEM)
+ EDIT_ITEM(bool, MSG_LCD_SOFT_ENDSTOPS, &soft_endstop._enabled);
+ #endif
+
+ if (NONE(IS_KINEMATIC, NO_MOTION_BEFORE_HOMING) || all_axes_homed()) {
+ if (TERN1(DELTA, current_position.z <= delta_clip_start_height)) {
+ SUBMENU(MSG_MOVE_X, []{ _menu_move_distance(X_AXIS, lcd_move_x); });
+ SUBMENU(MSG_MOVE_Y, []{ _menu_move_distance(Y_AXIS, lcd_move_y); });
+ }
+ #if ENABLED(DELTA)
+ else
+ ACTION_ITEM(MSG_FREE_XY, []{ line_to_z(delta_clip_start_height); ui.synchronize(); });
+ #endif
+
+ SUBMENU(MSG_MOVE_Z, []{ _menu_move_distance(Z_AXIS, lcd_move_z); });
+ }
+ else
+ GCODES_ITEM(MSG_AUTO_HOME, G28_STR);
+
+ #if ANY(SWITCHING_EXTRUDER, SWITCHING_NOZZLE, MAGNETIC_SWITCHING_TOOLHEAD)
+
+ #if EXTRUDERS >= 4
+ switch (active_extruder) {
+ case 0: GCODES_ITEM_N(1, MSG_SELECT_E, PSTR("T1")); break;
+ case 1: GCODES_ITEM_N(0, MSG_SELECT_E, PSTR("T0")); break;
+ case 2: GCODES_ITEM_N(3, MSG_SELECT_E, PSTR("T3")); break;
+ case 3: GCODES_ITEM_N(2, MSG_SELECT_E, PSTR("T2")); break;
+ #if EXTRUDERS == 6
+ case 4: GCODES_ITEM_N(5, MSG_SELECT_E, PSTR("T5")); break;
+ case 5: GCODES_ITEM_N(4, MSG_SELECT_E, PSTR("T4")); break;
+ #endif
+ }
+ #elif EXTRUDERS == 3
+ if (active_extruder < 2) {
+ if (active_extruder)
+ GCODES_ITEM_N(0, MSG_SELECT_E, PSTR("T0"));
+ else
+ GCODES_ITEM_N(1, MSG_SELECT_E, PSTR("T1"));
+ }
+ #else
+ if (active_extruder)
+ GCODES_ITEM_N(0, MSG_SELECT_E, PSTR("T0"));
+ else
+ GCODES_ITEM_N(1, MSG_SELECT_E, PSTR("T1"));
+ #endif
+
+ #elif ENABLED(DUAL_X_CARRIAGE)
+
+ if (active_extruder)
+ GCODES_ITEM_N(0, MSG_SELECT_E, PSTR("T0"));
+ else
+ GCODES_ITEM_N(1, MSG_SELECT_E, PSTR("T1"));
+
+ #endif
+
+ #if E_MANUAL
+
+ // The current extruder
+ SUBMENU(MSG_MOVE_E, []{ _menu_move_distance_e_maybe(); });
+
+ #define SUBMENU_MOVE_E(N) SUBMENU_N(N, MSG_MOVE_EN, []{ _menu_move_distance(E_AXIS, []{ lcd_move_e(MenuItemBase::itemIndex); }, MenuItemBase::itemIndex); });
+
+ #if EITHER(SWITCHING_EXTRUDER, SWITCHING_NOZZLE)
+
+ // ...and the non-switching
+ #if E_MANUAL == 7 || E_MANUAL == 5 || E_MANUAL == 3
+ SUBMENU_MOVE_E(E_MANUAL - 1);
+ #endif
+
+ #elif MULTI_MANUAL
+
+ // Independent extruders with one E-stepper per hotend
+ LOOP_L_N(n, E_MANUAL) SUBMENU_MOVE_E(n);
+
+ #endif
+
+ #endif // E_MANUAL
+
+ END_MENU();
+}
+
+#if ENABLED(AUTO_BED_LEVELING_UBL)
+ void _lcd_ubl_level_bed();
+#elif ENABLED(LCD_BED_LEVELING)
+ void menu_bed_leveling();
+#endif
+
+#if ENABLED(ASSISTED_TRAMMING_WIZARD)
+ void goto_tramming_wizard();
+#endif
+
+void menu_motion() {
+ START_MENU();
+
+ //
+ // ^ Main
+ //
+ BACK_ITEM(MSG_MAIN);
+
+ //
+ // Move Axis
+ //
+ if (TERN1(DELTA, all_axes_homed()))
+ SUBMENU(MSG_MOVE_AXIS, menu_move);
+
+ //
+ // Auto Home
+ //
+ GCODES_ITEM(MSG_AUTO_HOME, G28_STR);
+ #if ENABLED(INDIVIDUAL_AXIS_HOMING_MENU)
+ GCODES_ITEM(MSG_AUTO_HOME_X, PSTR("G28X"));
+ GCODES_ITEM(MSG_AUTO_HOME_Y, PSTR("G28Y"));
+ GCODES_ITEM(MSG_AUTO_HOME_Z, PSTR("G28Z"));
+ #endif
+
+ //
+ // Auto-calibration
+ //
+ #if ENABLED(CALIBRATION_GCODE)
+ GCODES_ITEM(MSG_AUTO_CALIBRATE, PSTR("G425"));
+ #endif
+
+ //
+ // Auto Z-Align
+ //
+ #if EITHER(Z_STEPPER_AUTO_ALIGN, MECHANICAL_GANTRY_CALIBRATION)
+ GCODES_ITEM(MSG_AUTO_Z_ALIGN, PSTR("G34"));
+ #endif
+
+ //
+ // Assisted Bed Tramming
+ //
+ #if ENABLED(ASSISTED_TRAMMING_WIZARD)
+ SUBMENU(MSG_TRAMMING_WIZARD, goto_tramming_wizard);
+ #endif
+
+ //
+ // Level Bed
+ //
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+
+ SUBMENU(MSG_UBL_LEVEL_BED, _lcd_ubl_level_bed);
+
+ #elif ENABLED(LCD_BED_LEVELING)
+
+ if (!g29_in_progress)
+ SUBMENU(MSG_BED_LEVELING, menu_bed_leveling);
+
+ #elif HAS_LEVELING && DISABLED(SLIM_LCD_MENUS)
+
+ #if DISABLED(PROBE_MANUALLY)
+ GCODES_ITEM(MSG_LEVEL_BED, PSTR("G29N"));
+ #endif
+
+ if (all_axes_homed() && leveling_is_valid()) {
+ bool show_state = planner.leveling_active;
+ EDIT_ITEM(bool, MSG_BED_LEVELING, &show_state, _lcd_toggle_bed_leveling);
+ }
+
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ editable.decimal = planner.z_fade_height;
+ EDIT_ITEM_FAST(float3, MSG_Z_FADE_HEIGHT, &editable.decimal, 0, 100, []{ set_z_fade_height(editable.decimal); });
+ #endif
+
+ #endif
+
+ #if ENABLED(LEVEL_BED_CORNERS) && DISABLED(LCD_BED_LEVELING)
+ SUBMENU(MSG_LEVEL_CORNERS, _lcd_level_bed_corners);
+ #endif
+
+ #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST)
+ GCODES_ITEM(MSG_M48_TEST, PSTR("G28O\nM48 P10"));
+ #endif
+
+ //
+ // Disable Steppers
+ //
+ GCODES_ITEM(MSG_DISABLE_STEPPERS, PSTR("M84"));
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU
diff --git a/Marlin/src/lcd/menu/menu_password.cpp b/Marlin/src/lcd/menu/menu_password.cpp
new file mode 100644
index 0000000..80c5c3d
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_password.cpp
@@ -0,0 +1,182 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Advanced Settings Menus
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, PASSWORD_FEATURE)
+
+#include "../../feature/password/password.h"
+
+#include "menu_item.h"
+#include "menu_addon.h"
+
+void menu_advanced_settings();
+
+screenFunc_t success_screen, fail_screen;
+bool authenticating; // = false
+char string[(PASSWORD_LENGTH) + 1];
+static uint8_t digit_no;
+
+//
+// Screen for both editing and setting the password
+//
+void Password::menu_password_entry() {
+ START_MENU();
+
+ // "Login" or "New Code"
+ STATIC_ITEM_P(authenticating ? GET_TEXT(MSG_LOGIN_REQUIRED) : GET_TEXT(MSG_EDIT_PASSWORD), SS_CENTER|SS_INVERT);
+
+ STATIC_ITEM_P(NUL_STR, SS_CENTER|SS_INVERT, string);
+
+ // Make the digit edit item look like a sub-menu
+ PGM_P const label = GET_TEXT(MSG_ENTER_DIGIT);
+ EDIT_ITEM_P(uint8, label, &editable.uint8, 0, 9, digit_entered);
+ MENU_ITEM_ADDON_START(utf8_strlen_P(label) + 1);
+ lcd_put_wchar(' ');
+ lcd_put_wchar('1' + digit_no);
+ SETCURSOR_X(LCD_WIDTH - 1);
+ lcd_put_wchar('>');
+ MENU_ITEM_ADDON_END();
+
+ ACTION_ITEM(MSG_START_OVER, start_over);
+
+ if (!authenticating) BACK_ITEM(MSG_BUTTON_CANCEL);
+
+ END_MENU();
+}
+
+//
+// Authentication check
+//
+void Password::authentication_done() {
+ ui.goto_screen(is_locked ? fail_screen : success_screen);
+ ui.completion_feedback(!is_locked);
+}
+
+// A single digit was completed
+void Password::digit_entered() {
+ uint32_t multiplier = CAT(1e, PASSWORD_LENGTH); // 1e5 = 100000
+ LOOP_LE_N(i, digit_no) multiplier /= 10;
+ value_entry += editable.uint8 * multiplier;
+ string[digit_no++] = '0' + editable.uint8;
+
+ // Exit edit screen menu and go to another screen
+ ui.goto_previous_screen();
+ ui.use_click();
+ ui.goto_screen(menu_password_entry);
+
+ // After password has been keyed in
+ if (digit_no == PASSWORD_LENGTH) {
+ if (authenticating)
+ authentication_check();
+ else
+ set_password_done();
+ }
+}
+
+//
+// Set/Change Password
+//
+void Password::screen_password_entry() {
+ value_entry = 0;
+ digit_no = 0;
+ editable.uint8 = 0;
+ memset(string, '-', PASSWORD_LENGTH);
+ string[PASSWORD_LENGTH] = '\0';
+ menu_password_entry();
+}
+
+void Password::screen_set_password() {
+ authenticating = false;
+ screen_password_entry();
+}
+
+void Password::authenticate_user(const screenFunc_t in_succ_scr, const screenFunc_t in_fail_scr) {
+ success_screen = in_succ_scr;
+ fail_screen = in_fail_scr;
+ if (is_set) {
+ authenticating = true;
+ ui.goto_screen(screen_password_entry);
+ ui.defer_status_screen();
+ ui.update();
+ }
+ else {
+ ui.goto_screen(in_succ_scr);
+ is_locked = false;
+ }
+}
+
+void Password::access_menu_password() {
+ authenticate_user(menu_password, menu_advanced_settings);
+}
+
+#if ENABLED(PASSWORD_ON_SD_PRINT_MENU)
+ void Password::media_gatekeeper() {
+ authenticate_user(menu_media, menu_main);
+ }
+#endif
+
+void Password::start_over() {
+ ui.goto_previous_screen(); // Goto previous screen, if any
+ ui.goto_screen(screen_password_entry);
+}
+
+void Password::menu_password_report() {
+ START_SCREEN();
+ BACK_ITEM(MSG_PASSWORD_SETTINGS);
+ STATIC_ITEM(MSG_PASSWORD_SET, SS_LEFT, string);
+ STATIC_ITEM(MSG_REMINDER_SAVE_SETTINGS, SS_LEFT);
+ END_SCREEN();
+}
+
+void Password::set_password_done(const bool with_set/*=true*/) {
+ is_set = with_set;
+ value = value_entry;
+ ui.completion_feedback(true);
+ ui.goto_screen(menu_password_report);
+}
+
+void Password::remove_password() {
+ string[0] = '0';
+ string[1] = '\0';
+ set_password_done(false);
+}
+
+//
+// Password Menu
+//
+void Password::menu_password() {
+ START_MENU();
+ BACK_ITEM(MSG_ADVANCED_SETTINGS);
+ SUBMENU(MSG_CHANGE_PASSWORD, screen_set_password);
+ ACTION_ITEM(MSG_REMOVE_PASSWORD, []{ ui.save_previous_screen(); remove_password(); } );
+ #if ENABLED(EEPROM_SETTINGS)
+ ACTION_ITEM(MSG_STORE_EEPROM, ui.store_settings);
+ #endif
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && PASSWORD_FEATURE
diff --git a/Marlin/src/lcd/menu/menu_power_monitor.cpp b/Marlin/src/lcd/menu/menu_power_monitor.cpp
new file mode 100644
index 0000000..d31ebd3
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_power_monitor.cpp
@@ -0,0 +1,62 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Power Monitor Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_LCD_MENU && HAS_POWER_MONITOR
+
+#include "menu_item.h"
+#include "../../feature/power_monitor.h"
+
+void menu_power_monitor() {
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ #if ENABLED(POWER_MONITOR_CURRENT)
+ {
+ bool ena = power_monitor.current_display_enabled();
+ EDIT_ITEM(bool, MSG_CURRENT, &ena, power_monitor.toggle_current_display);
+ }
+ #endif
+
+ #if HAS_POWER_MONITOR_VREF
+ {
+ bool ena = power_monitor.voltage_display_enabled();
+ EDIT_ITEM(bool, MSG_VOLTAGE, &ena, power_monitor.toggle_voltage_display);
+ }
+ #endif
+
+ #if HAS_POWER_MONITOR_WATTS
+ {
+ bool ena = power_monitor.power_display_enabled();
+ EDIT_ITEM(bool, MSG_POWER, &ena, power_monitor.toggle_power_display);
+ }
+ #endif
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && HAS_POWER_MONITOR
diff --git a/Marlin/src/lcd/menu/menu_probe_offset.cpp b/Marlin/src/lcd/menu/menu_probe_offset.cpp
new file mode 100644
index 0000000..2f0c37b
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_probe_offset.cpp
@@ -0,0 +1,190 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Calibrate Probe offset menu.
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(PROBE_OFFSET_WIZARD)
+
+#include "menu_item.h"
+#include "menu_addon.h"
+#include "../../gcode/queue.h"
+#include "../../module/motion.h"
+#include "../../module/planner.h"
+#include "../../module/probe.h"
+
+#if HAS_LEVELING
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+// Global storage
+float z_offset_backup, calculated_z_offset, z_offset_ref;
+
+TERN_(HAS_LEVELING, bool leveling_was_active);
+
+inline void z_clearance_move() {
+ do_z_clearance(
+ #ifdef Z_AFTER_HOMING
+ Z_AFTER_HOMING
+ #elif defined(Z_HOMING_HEIGHT)
+ Z_HOMING_HEIGHT
+ #else
+ 10
+ #endif
+ );
+}
+
+void set_offset_and_go_back(const float &z) {
+ probe.offset.z = z;
+ SET_SOFT_ENDSTOP_LOOSE(false);
+ TERN_(HAS_LEVELING, set_bed_leveling_enabled(leveling_was_active));
+ ui.goto_previous_screen_no_defer();
+}
+
+void _goto_manual_move_z(const float scale) {
+ ui.manual_move.menu_scale = scale;
+ ui.goto_screen(lcd_move_z);
+}
+
+void probe_offset_wizard_menu() {
+ START_MENU();
+ calculated_z_offset = probe.offset.z + current_position.z - z_offset_ref;
+
+ if (LCD_HEIGHT >= 4)
+ STATIC_ITEM(MSG_MOVE_NOZZLE_TO_BED, SS_CENTER|SS_INVERT);
+
+ STATIC_ITEM_P(PSTR("Z="), SS_CENTER, ftostr42_52(current_position.z));
+ STATIC_ITEM(MSG_ZPROBE_ZOFFSET, SS_LEFT, ftostr42_52(calculated_z_offset));
+
+ SUBMENU(MSG_MOVE_1MM, []{ _goto_manual_move_z( 1); });
+ SUBMENU(MSG_MOVE_01MM, []{ _goto_manual_move_z( 0.1f); });
+
+ if ((FINE_MANUAL_MOVE) > 0.0f && (FINE_MANUAL_MOVE) < 0.1f) {
+ char tmp[20], numstr[10];
+ // Determine digits needed right of decimal
+ const uint8_t digs = !UNEAR_ZERO((FINE_MANUAL_MOVE) * 1000 - int((FINE_MANUAL_MOVE) * 1000)) ? 4 :
+ !UNEAR_ZERO((FINE_MANUAL_MOVE) * 100 - int((FINE_MANUAL_MOVE) * 100)) ? 3 : 2;
+ sprintf_P(tmp, GET_TEXT(MSG_MOVE_N_MM), dtostrf(FINE_MANUAL_MOVE, 1, digs, numstr));
+ #if DISABLED(HAS_GRAPHICAL_TFT)
+ SUBMENU_P(NUL_STR, []{ _goto_manual_move_z(float(FINE_MANUAL_MOVE)); });
+ MENU_ITEM_ADDON_START(0 + ENABLED(HAS_MARLINUI_HD44780));
+ lcd_put_u8str(tmp);
+ MENU_ITEM_ADDON_END();
+ #else
+ SUBMENU_P(tmp, []{ _goto_manual_move_z(float(FINE_MANUAL_MOVE)); });
+ #endif
+ }
+
+ ACTION_ITEM(MSG_BUTTON_DONE, []{
+ set_offset_and_go_back(calculated_z_offset);
+ current_position.z = z_offset_ref; // Set Z to z_offset_ref, as we can expect it is at probe height
+ sync_plan_position();
+ z_clearance_move(); // Raise Z as if it was homed
+ });
+
+ ACTION_ITEM(MSG_BUTTON_CANCEL, []{
+ set_offset_and_go_back(z_offset_backup);
+ // If wizard-homing was done by probe with PROBE_OFFSET_WIZARD_START_Z
+ #if HOMING_Z_WITH_PROBE && defined(PROBE_OFFSET_WIZARD_START_Z)
+ set_axis_never_homed(Z_AXIS); // On cancel the Z position needs correction
+ queue.inject_P(PSTR("G28Z"));
+ #else // Otherwise do a Z clearance move like after Homing
+ z_clearance_move();
+ #endif
+ });
+
+ END_MENU();
+}
+
+void prepare_for_probe_offset_wizard() {
+ #if defined(PROBE_OFFSET_WIZARD_XY_POS) || !HOMING_Z_WITH_PROBE
+ if (ui.should_draw()) MenuItem_static::draw(1, GET_TEXT(MSG_PROBE_WIZARD_PROBING));
+
+ if (ui.wait_for_move) return;
+
+ #ifndef PROBE_OFFSET_WIZARD_XY_POS
+ #define PROBE_OFFSET_WIZARD_XY_POS XY_CENTER
+ #endif
+ // Get X and Y from configuration, or use center
+ constexpr xy_pos_t wizard_pos = PROBE_OFFSET_WIZARD_XY_POS;
+
+ // Probe for Z reference
+ ui.wait_for_move = true;
+ z_offset_ref = probe.probe_at_point(wizard_pos, PROBE_PT_RAISE, 0, true);
+ ui.wait_for_move = false;
+
+ // Stow the probe, as the last call to probe.probe_at_point(...) left
+ // the probe deployed if it was successful.
+ probe.stow();
+ #else
+ if (ui.wait_for_move) return;
+ #endif
+
+ // Move Nozzle to Probing/Homing Position
+ ui.wait_for_move = true;
+ current_position += probe.offset_xy;
+ line_to_current_position(MMM_TO_MMS(XY_PROBE_SPEED));
+ ui.synchronize(GET_TEXT(MSG_PROBE_WIZARD_MOVING));
+ ui.wait_for_move = false;
+
+ SET_SOFT_ENDSTOP_LOOSE(true); // Disable soft endstops for free Z movement
+
+ // Go to Calibration Menu
+ ui.goto_screen(probe_offset_wizard_menu);
+ ui.defer_status_screen();
+}
+
+void goto_probe_offset_wizard() {
+ ui.defer_status_screen();
+ set_all_unhomed();
+
+ // Store probe.offset.z for Case: Cancel
+ z_offset_backup = probe.offset.z;
+
+ #ifdef PROBE_OFFSET_WIZARD_START_Z
+ probe.offset.z = PROBE_OFFSET_WIZARD_START_Z;
+ #endif
+
+ // Store Bed-Leveling-State and disable
+ #if HAS_LEVELING
+ leveling_was_active = planner.leveling_active;
+ set_bed_leveling_enabled(false);
+ #endif
+
+ // Home all axes
+ queue.inject_P(G28_STR);
+
+ ui.goto_screen([]{
+ _lcd_draw_homing();
+ if (all_axes_homed()) {
+ z_offset_ref = 0; // Set Z Value for Wizard Position to 0
+ ui.goto_screen(prepare_for_probe_offset_wizard);
+ ui.defer_status_screen();
+ }
+ });
+
+}
+
+#endif // PROBE_OFFSET_WIZARD
diff --git a/Marlin/src/lcd/menu/menu_spindle_laser.cpp b/Marlin/src/lcd/menu/menu_spindle_laser.cpp
new file mode 100644
index 0000000..93ef224
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_spindle_laser.cpp
@@ -0,0 +1,74 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Spindle / Laser Menu
+//
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_LCD_MENU && HAS_CUTTER
+
+ #include "menu_item.h"
+
+ #include "../../feature/spindle_laser.h"
+
+ void menu_spindle_laser() {
+ bool is_enabled = cutter.enabled() && cutter.isReady;
+ #if ENABLED(SPINDLE_CHANGE_DIR)
+ bool is_rev = cutter.is_reverse();
+ #endif
+
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ #if ENABLED(SPINDLE_LASER_PWM)
+ // Change the cutter's "current power" value without turning the cutter on or off
+ // Power is displayed and set in units and range according to CUTTER_POWER_UNIT
+ EDIT_ITEM_FAST(CUTTER_MENU_POWER_TYPE, MSG_CUTTER(POWER), &cutter.menuPower,
+ cutter.mpower_min(), cutter.mpower_max(), cutter.update_from_mpower);
+ #endif
+
+ editable.state = is_enabled;
+ EDIT_ITEM(bool, MSG_CUTTER(TOGGLE), &is_enabled, []{ if (editable.state) cutter.disable(); else cutter.enable_same_dir(); });
+
+ #if ENABLED(SPINDLE_CHANGE_DIR)
+ if (!is_enabled) {
+ editable.state = is_rev;
+ ACTION_ITEM_P(is_rev ? GET_TEXT(MSG_CUTTER(REVERSE)) : GET_TEXT(MSG_CUTTER(FORWARD)), []{ cutter.set_reverse(!editable.state); });
+ }
+ #endif
+
+ #if ENABLED(LASER_FEATURE)
+ // Setup and fire a test pulse using the current PWM power level for for a duration of test_pulse_min to test_pulse_max ms.
+ EDIT_ITEM_FAST(CUTTER_MENU_PULSE_TYPE, MSG_LASER_PULSE_MS, &cutter.testPulse, LASER_TEST_PULSE_MIN, LASER_TEST_PULSE_MAX);
+ ACTION_ITEM(MSG_LASER_FIRE_PULSE, cutter.test_fire_pulse);
+ #endif
+
+ #if BOTH(MARLIN_DEV_MODE, HAL_CAN_SET_PWM_FREQ) && defined(SPINDLE_LASER_FREQUENCY)
+ EDIT_ITEM_FAST(CUTTER_MENU_FREQUENCY_TYPE, MSG_CUTTER_FREQUENCY, &cutter.frequency, 2000, 80000, cutter.refresh_frequency);
+ #endif
+
+ END_MENU();
+ }
+
+#endif // HAS_LCD_MENU && HAS_CUTTER
diff --git a/Marlin/src/lcd/menu/menu_temperature.cpp b/Marlin/src/lcd/menu/menu_temperature.cpp
new file mode 100644
index 0000000..01c1f8f
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_temperature.cpp
@@ -0,0 +1,252 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Temperature Menu
+//
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_LCD_MENU && HAS_TEMPERATURE
+
+#include "menu_item.h"
+#include "../../module/temperature.h"
+
+#if HAS_FAN || ENABLED(SINGLENOZZLE)
+ #include "../../module/motion.h"
+#endif
+
+#if ENABLED(SINGLENOZZLE_STANDBY_TEMP)
+ #include "../../module/tool_change.h"
+#endif
+
+//
+// "Temperature" submenu items
+//
+
+void Temperature::lcd_preheat(const int16_t e, const int8_t indh, const int8_t indb) {
+ UNUSED(e); UNUSED(indh); UNUSED(indb);
+ #if HAS_HOTEND
+ if (indh >= 0 && ui.material_preset[indh].hotend_temp > 0)
+ setTargetHotend(_MIN(thermalManager.heater_maxtemp[e] - HOTEND_OVERSHOOT, ui.material_preset[indh].hotend_temp), e);
+ #endif
+ #if HAS_HEATED_BED
+ if (indb >= 0 && ui.material_preset[indb].bed_temp > 0) setTargetBed(ui.material_preset[indb].bed_temp);
+ #endif
+ #if HAS_FAN
+ if (indh >= 0)
+ set_fan_speed(active_extruder < (FAN_COUNT) ? active_extruder : 0, ui.material_preset[indh].fan_speed);
+ #endif
+ ui.return_to_status();
+}
+
+#if PREHEAT_COUNT
+
+ #if HAS_TEMP_HOTEND
+ inline void _preheat_end(const uint8_t m, const uint8_t e) { thermalManager.lcd_preheat(e, m, -1); }
+ void do_preheat_end_m() { _preheat_end(editable.int8, 0); }
+ #endif
+ #if HAS_HEATED_BED
+ inline void _preheat_bed(const uint8_t m) { thermalManager.lcd_preheat(-1, -1, m); }
+ #endif
+
+ #if HAS_TEMP_HOTEND && HAS_HEATED_BED
+ inline void _preheat_both(const uint8_t m, const uint8_t e) { thermalManager.lcd_preheat(e, m, m); }
+
+ // Indexed "Preheat ABC" and "Heat Bed" items
+ #define PREHEAT_ITEMS(M,E) do{ \
+ ACTION_ITEM_N_S(E, ui.get_preheat_label(M), MSG_PREHEAT_M_H, []{ _preheat_both(M, MenuItemBase::itemIndex); }); \
+ ACTION_ITEM_N_S(E, ui.get_preheat_label(M), MSG_PREHEAT_M_END_E, []{ _preheat_end(M, MenuItemBase::itemIndex); }); \
+ }while(0)
+
+ #elif HAS_MULTI_HOTEND
+
+ // No heated bed, so just indexed "Preheat ABC" items
+ #define PREHEAT_ITEMS(M,E) ACTION_ITEM_N_S(E, ui.get_preheat_label(M), MSG_PREHEAT_M_H, []{ _preheat_end(M, MenuItemBase::itemIndex); })
+
+ #endif
+
+ #if HAS_MULTI_HOTEND || HAS_HEATED_BED
+
+ // Set editable.int8 to the Material index before entering this menu
+ // because MenuItemBase::itemIndex will be re-used by PREHEAT_ITEMS
+ void menu_preheat_m() {
+ const uint8_t m = editable.int8; // Don't re-use 'editable' in this menu
+
+ START_MENU();
+ BACK_ITEM(MSG_TEMPERATURE);
+
+ #if HOTENDS == 1
+
+ #if HAS_HEATED_BED
+ ACTION_ITEM_S(ui.get_preheat_label(m), MSG_PREHEAT_M, []{ _preheat_both(editable.int8, 0); });
+ ACTION_ITEM_S(ui.get_preheat_label(m), MSG_PREHEAT_M_END, do_preheat_end_m);
+ #else
+ ACTION_ITEM_S(ui.get_preheat_label(m), MSG_PREHEAT_M, do_preheat_end_m);
+ #endif
+
+ #elif HAS_MULTI_HOTEND
+
+ HOTEND_LOOP() PREHEAT_ITEMS(editable.int8, e);
+ ACTION_ITEM_S(ui.get_preheat_label(m), MSG_PREHEAT_M_ALL, []() {
+ HOTEND_LOOP() thermalManager.setTargetHotend(ui.material_preset[editable.int8].hotend_temp, e);
+ TERN(HAS_HEATED_BED, _preheat_bed(editable.int8), ui.return_to_status());
+ });
+
+ #endif
+
+ #if HAS_HEATED_BED
+ ACTION_ITEM_S(ui.get_preheat_label(m), MSG_PREHEAT_M_BEDONLY, []{ _preheat_bed(editable.int8); });
+ #endif
+
+ END_MENU();
+ }
+
+ #endif // HAS_MULTI_HOTEND || HAS_HEATED_BED
+
+#endif // PREHEAT_COUNT
+
+#if HAS_TEMP_HOTEND || HAS_HEATED_BED
+
+ void lcd_cooldown() {
+ thermalManager.zero_fan_speeds();
+ thermalManager.disable_all_heaters();
+ ui.return_to_status();
+ }
+
+#endif // HAS_TEMP_HOTEND || HAS_HEATED_BED
+
+void menu_temperature() {
+ #if HAS_TEMP_HOTEND || HAS_HEATED_BED
+ bool has_heat = false;
+ #if HAS_TEMP_HOTEND
+ HOTEND_LOOP() if (thermalManager.temp_hotend[HOTEND_INDEX].target) { has_heat = true; break; }
+ #endif
+ #endif
+
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ //
+ // Nozzle:
+ // Nozzle [1-5]:
+ //
+ #if HOTENDS == 1
+ EDIT_ITEM_FAST(int3, MSG_NOZZLE, &thermalManager.temp_hotend[0].target, 0, HEATER_0_MAXTEMP - (HOTEND_OVERSHOOT), []{ thermalManager.start_watching_hotend(0); });
+ #elif HAS_MULTI_HOTEND
+ HOTEND_LOOP()
+ EDIT_ITEM_FAST_N(int3, e, MSG_NOZZLE_N, &thermalManager.temp_hotend[e].target, 0, thermalManager.heater_maxtemp[e] - (HOTEND_OVERSHOOT), []{ thermalManager.start_watching_hotend(MenuItemBase::itemIndex); });
+ #endif
+
+ #if ENABLED(SINGLENOZZLE_STANDBY_TEMP)
+ LOOP_S_L_N(e, 1, EXTRUDERS)
+ EDIT_ITEM_FAST_N(uint16_3, e, MSG_NOZZLE_STANDBY, &thermalManager.singlenozzle_temp[e], 0, thermalManager.heater_maxtemp[0] - (HOTEND_OVERSHOOT));
+ #endif
+
+ //
+ // Bed:
+ //
+ #if HAS_HEATED_BED
+ EDIT_ITEM_FAST(int3, MSG_BED, &thermalManager.temp_bed.target, 0, BED_MAX_TARGET, thermalManager.start_watching_bed);
+ #endif
+
+ //
+ // Chamber:
+ //
+ #if HAS_HEATED_CHAMBER
+ EDIT_ITEM_FAST(int3, MSG_CHAMBER, &thermalManager.temp_chamber.target, 0, CHAMBER_MAXTEMP - 10, thermalManager.start_watching_chamber);
+ #endif
+
+ //
+ // Fan Speed:
+ //
+ #if HAS_FAN
+
+ DEFINE_SINGLENOZZLE_ITEM();
+
+ #if HAS_FAN0
+ _FAN_EDIT_ITEMS(0,FIRST_FAN_SPEED);
+ #endif
+ #if HAS_FAN1
+ FAN_EDIT_ITEMS(1);
+ #elif SNFAN(1)
+ singlenozzle_item(1);
+ #endif
+ #if HAS_FAN2
+ FAN_EDIT_ITEMS(2);
+ #elif SNFAN(2)
+ singlenozzle_item(2);
+ #endif
+ #if HAS_FAN3
+ FAN_EDIT_ITEMS(3);
+ #elif SNFAN(3)
+ singlenozzle_item(3);
+ #endif
+ #if HAS_FAN4
+ FAN_EDIT_ITEMS(4);
+ #elif SNFAN(4)
+ singlenozzle_item(4);
+ #endif
+ #if HAS_FAN5
+ FAN_EDIT_ITEMS(5);
+ #elif SNFAN(5)
+ singlenozzle_item(5);
+ #endif
+ #if HAS_FAN6
+ FAN_EDIT_ITEMS(6);
+ #elif SNFAN(6)
+ singlenozzle_item(6);
+ #endif
+ #if HAS_FAN7
+ FAN_EDIT_ITEMS(7);
+ #elif SNFAN(7)
+ singlenozzle_item(7);
+ #endif
+
+ #endif // HAS_FAN
+
+ #if PREHEAT_COUNT
+ //
+ // Preheat for Materials 1 to 5
+ //
+ LOOP_L_N(m, PREHEAT_COUNT) {
+ editable.int8 = m;
+ #if HOTENDS > 1 || HAS_HEATED_BED
+ SUBMENU_S(ui.get_preheat_label(m), MSG_PREHEAT_M, menu_preheat_m);
+ #else
+ ACTION_ITEM_S(ui.get_preheat_label(m), MSG_PREHEAT_M, do_preheat_end_m);
+ #endif
+ }
+ #endif
+
+ #if HAS_TEMP_HOTEND || HAS_HEATED_BED
+ //
+ // Cooldown
+ //
+ if (TERN0(HAS_HEATED_BED, thermalManager.temp_bed.target)) has_heat = true;
+ if (has_heat) ACTION_ITEM(MSG_COOLDOWN, lcd_cooldown);
+ #endif
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && HAS_TEMPERATURE
diff --git a/Marlin/src/lcd/menu/menu_tmc.cpp b/Marlin/src/lcd/menu/menu_tmc.cpp
new file mode 100644
index 0000000..6919370
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_tmc.cpp
@@ -0,0 +1,264 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// TMC Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_LCD_MENU && HAS_TRINAMIC_CONFIG
+
+#include "menu_item.h"
+#include "../../module/stepper/indirection.h"
+#include "../../feature/tmc_util.h"
+
+#define TMC_EDIT_STORED_I_RMS(ST,STR) EDIT_ITEM_P(uint16_4, PSTR(STR), &stepper##ST.val_mA, 100, 3000, []{ stepper##ST.refresh_stepper_current(); })
+
+void menu_tmc_current() {
+ START_MENU();
+ BACK_ITEM(MSG_TMC_DRIVERS);
+ #if AXIS_IS_TMC(X)
+ TMC_EDIT_STORED_I_RMS(X, STR_X);
+ #endif
+ #if AXIS_IS_TMC(Y)
+ TMC_EDIT_STORED_I_RMS(Y, STR_Y);
+ #endif
+ #if AXIS_IS_TMC(Z)
+ TMC_EDIT_STORED_I_RMS(Z, STR_Z);
+ #endif
+ #if AXIS_IS_TMC(X2)
+ TMC_EDIT_STORED_I_RMS(X2, STR_X2);
+ #endif
+ #if AXIS_IS_TMC(Y2)
+ TMC_EDIT_STORED_I_RMS(Y2, STR_Y2);
+ #endif
+ #if AXIS_IS_TMC(Z2)
+ TMC_EDIT_STORED_I_RMS(Z2, STR_Z2);
+ #endif
+ #if AXIS_IS_TMC(Z3)
+ TMC_EDIT_STORED_I_RMS(Z3, STR_Z3);
+ #endif
+ #if AXIS_IS_TMC(Z4)
+ TMC_EDIT_STORED_I_RMS(Z4, STR_Z4);
+ #endif
+ #if AXIS_IS_TMC(E0)
+ TMC_EDIT_STORED_I_RMS(E0, LCD_STR_E0);
+ #endif
+ #if AXIS_IS_TMC(E1)
+ TMC_EDIT_STORED_I_RMS(E1, LCD_STR_E1);
+ #endif
+ #if AXIS_IS_TMC(E2)
+ TMC_EDIT_STORED_I_RMS(E2, LCD_STR_E2);
+ #endif
+ #if AXIS_IS_TMC(E3)
+ TMC_EDIT_STORED_I_RMS(E3, LCD_STR_E3);
+ #endif
+ #if AXIS_IS_TMC(E4)
+ TMC_EDIT_STORED_I_RMS(E4, LCD_STR_E4);
+ #endif
+ #if AXIS_IS_TMC(E5)
+ TMC_EDIT_STORED_I_RMS(E5, LCD_STR_E5);
+ #endif
+ #if AXIS_IS_TMC(E6)
+ TMC_EDIT_STORED_I_RMS(E6, LCD_STR_E6);
+ #endif
+ #if AXIS_IS_TMC(E7)
+ TMC_EDIT_STORED_I_RMS(E7, LCD_STR_E7);
+ #endif
+ END_MENU();
+}
+
+#if ENABLED(HYBRID_THRESHOLD)
+
+ #define TMC_EDIT_STORED_HYBRID_THRS(ST, STR) EDIT_ITEM_P(uint8, PSTR(STR), &stepper##ST.stored.hybrid_thrs, 0, 255, []{ stepper##ST.refresh_hybrid_thrs(); });
+
+ void menu_tmc_hybrid_thrs() {
+ START_MENU();
+ BACK_ITEM(MSG_TMC_DRIVERS);
+ #if AXIS_HAS_STEALTHCHOP(X)
+ TMC_EDIT_STORED_HYBRID_THRS(X, STR_X);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Y)
+ TMC_EDIT_STORED_HYBRID_THRS(Y, STR_Y);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z)
+ TMC_EDIT_STORED_HYBRID_THRS(Z, STR_Z);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(X2)
+ TMC_EDIT_STORED_HYBRID_THRS(X2, STR_X2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Y2)
+ TMC_EDIT_STORED_HYBRID_THRS(Y2, STR_Y2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z2)
+ TMC_EDIT_STORED_HYBRID_THRS(Z2, STR_Z2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z3)
+ TMC_EDIT_STORED_HYBRID_THRS(Z3, STR_Z3);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z4)
+ TMC_EDIT_STORED_HYBRID_THRS(Z4, STR_Z4);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E0)
+ TMC_EDIT_STORED_HYBRID_THRS(E0, LCD_STR_E0);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E1)
+ TMC_EDIT_STORED_HYBRID_THRS(E1, LCD_STR_E1);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E2)
+ TMC_EDIT_STORED_HYBRID_THRS(E2, LCD_STR_E2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E3)
+ TMC_EDIT_STORED_HYBRID_THRS(E3, LCD_STR_E3);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E4)
+ TMC_EDIT_STORED_HYBRID_THRS(E4, LCD_STR_E4);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E5)
+ TMC_EDIT_STORED_HYBRID_THRS(E5, LCD_STR_E5);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E6)
+ TMC_EDIT_STORED_HYBRID_THRS(E6, LCD_STR_E6);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E7)
+ TMC_EDIT_STORED_HYBRID_THRS(E7, LCD_STR_E7);
+ #endif
+ END_MENU();
+ }
+
+#endif
+
+#if ENABLED(SENSORLESS_HOMING)
+
+ #define TMC_EDIT_STORED_SGT(ST) EDIT_ITEM_P(int4, PSTR(STR_##ST), &stepper##ST.stored.homing_thrs, stepper##ST.sgt_min, stepper##ST.sgt_max, []{ stepper##ST.refresh_homing_thrs(); });
+
+ void menu_tmc_homing_thrs() {
+ START_MENU();
+ BACK_ITEM(MSG_TMC_DRIVERS);
+ #if X_SENSORLESS
+ TMC_EDIT_STORED_SGT(X);
+ #if X2_SENSORLESS
+ TMC_EDIT_STORED_SGT(X2);
+ #endif
+ #endif
+ #if Y_SENSORLESS
+ TMC_EDIT_STORED_SGT(Y);
+ #if Y2_SENSORLESS
+ TMC_EDIT_STORED_SGT(Y2);
+ #endif
+ #endif
+ #if Z_SENSORLESS
+ TMC_EDIT_STORED_SGT(Z);
+ #if Z2_SENSORLESS
+ TMC_EDIT_STORED_SGT(Z2);
+ #endif
+ #if Z3_SENSORLESS
+ TMC_EDIT_STORED_SGT(Z3);
+ #endif
+ #if Z4_SENSORLESS
+ TMC_EDIT_STORED_SGT(Z4);
+ #endif
+ #endif
+ END_MENU();
+ }
+
+#endif
+
+#if HAS_STEALTHCHOP
+
+ #define TMC_EDIT_STEP_MODE(ST, STR) EDIT_ITEM_P(bool, PSTR(STR), &stepper##ST.stored.stealthChop_enabled, []{ stepper##ST.refresh_stepping_mode(); })
+
+ void menu_tmc_step_mode() {
+ START_MENU();
+ STATIC_ITEM(MSG_TMC_STEALTH_ENABLED);
+ BACK_ITEM(MSG_TMC_DRIVERS);
+ #if AXIS_HAS_STEALTHCHOP(X)
+ TMC_EDIT_STEP_MODE(X, STR_X);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Y)
+ TMC_EDIT_STEP_MODE(Y, STR_Y);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z)
+ TMC_EDIT_STEP_MODE(Z, STR_Z);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(X2)
+ TMC_EDIT_STEP_MODE(X2, STR_X2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Y2)
+ TMC_EDIT_STEP_MODE(Y2, STR_Y2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z2)
+ TMC_EDIT_STEP_MODE(Z2, STR_Z2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z3)
+ TMC_EDIT_STEP_MODE(Z3, STR_Z3);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z4)
+ TMC_EDIT_STEP_MODE(Z4, STR_Z4);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E0)
+ TMC_EDIT_STEP_MODE(E0, LCD_STR_E0);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E1)
+ TMC_EDIT_STEP_MODE(E1, LCD_STR_E1);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E2)
+ TMC_EDIT_STEP_MODE(E2, LCD_STR_E2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E3)
+ TMC_EDIT_STEP_MODE(E3, LCD_STR_E3);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E4)
+ TMC_EDIT_STEP_MODE(E4, LCD_STR_E4);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E5)
+ TMC_EDIT_STEP_MODE(E5, LCD_STR_E5);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E6)
+ TMC_EDIT_STEP_MODE(E6, LCD_STR_E6);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E7)
+ TMC_EDIT_STEP_MODE(E7, LCD_STR_E7);
+ #endif
+ END_MENU();
+ }
+
+#endif
+
+void menu_tmc() {
+ START_MENU();
+ BACK_ITEM(MSG_ADVANCED_SETTINGS);
+ SUBMENU(MSG_TMC_CURRENT, menu_tmc_current);
+ #if ENABLED(HYBRID_THRESHOLD)
+ SUBMENU(MSG_TMC_HYBRID_THRS, menu_tmc_hybrid_thrs);
+ #endif
+ #if ENABLED(SENSORLESS_HOMING)
+ SUBMENU(MSG_TMC_HOMING_THRS, menu_tmc_homing_thrs);
+ #endif
+ #if HAS_STEALTHCHOP
+ SUBMENU(MSG_TMC_STEPPING_MODE, menu_tmc_step_mode);
+ #endif
+ END_MENU();
+}
+
+#endif // HAS_TRINAMIC_CONFIG
diff --git a/Marlin/src/lcd/menu/menu_touch_screen.cpp b/Marlin/src/lcd/menu/menu_touch_screen.cpp
new file mode 100644
index 0000000..5fc4b58
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_touch_screen.cpp
@@ -0,0 +1,36 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, TOUCH_SCREEN_CALIBRATION)
+
+#include "menu_item.h"
+#include "../marlinui.h"
+
+void touch_screen_calibration() {
+
+ ui.touch_calibration_screen();
+
+}
+
+#endif // TOUCH_SCREEN_CALIBRATION
diff --git a/Marlin/src/lcd/menu/menu_tramming.cpp b/Marlin/src/lcd/menu/menu_tramming.cpp
new file mode 100644
index 0000000..da7afd8
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_tramming.cpp
@@ -0,0 +1,104 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Bed Tramming Wizard
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, ASSISTED_TRAMMING_WIZARD)
+
+#include "menu_item.h"
+
+#include "../../feature/tramming.h"
+
+#include "../../module/motion.h"
+#include "../../module/probe.h"
+#include "../../gcode/queue.h"
+
+//#define DEBUG_OUT 1
+#include "../../core/debug_out.h"
+
+float z_measured[G35_PROBE_COUNT] = { 0 };
+static uint8_t tram_index = 0;
+
+#if HAS_LEVELING
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+static bool probe_single_point() {
+ do_blocking_move_to_z(TERN(BLTOUCH, Z_CLEARANCE_DEPLOY_PROBE, Z_CLEARANCE_BETWEEN_PROBES));
+ // Stow after each point with BLTouch "HIGH SPEED" mode for push-pin safety
+ const float z_probed_height = probe.probe_at_point(screws_tilt_adjust_pos[tram_index], TERN(BLTOUCH_HS_MODE, PROBE_PT_STOW, PROBE_PT_RAISE), 0, true);
+ DEBUG_ECHOLNPAIR("probe_single_point: ", z_probed_height, "mm");
+ z_measured[tram_index] = z_probed_height;
+ move_to_tramming_wait_pos();
+
+ return !isnan(z_probed_height);
+}
+
+static void _menu_single_probe(const uint8_t point) {
+ tram_index = point;
+ DEBUG_ECHOLNPAIR("Screen: single probe screen Arg:", point);
+ START_MENU();
+ STATIC_ITEM(MSG_LEVEL_CORNERS, SS_LEFT);
+ STATIC_ITEM(MSG_LAST_VALUE_SP, SS_LEFT, ftostr42_52(z_measured[0] - z_measured[point])); // Print diff
+ ACTION_ITEM(MSG_UBL_BC_INSERT2, []{ if (probe_single_point()) ui.refresh(); });
+ ACTION_ITEM(MSG_BUTTON_DONE, []{ ui.goto_previous_screen(); }); // Back
+ END_MENU();
+}
+
+static void tramming_wizard_menu() {
+ DEBUG_ECHOLNPAIR("Screen: tramming_wizard_menu");
+ START_MENU();
+ STATIC_ITEM(MSG_SELECT_ORIGIN);
+
+ // Draw a menu item for each tramming point
+ LOOP_L_N(i, G35_PROBE_COUNT)
+ SUBMENU_N_P(i, (char*)pgm_read_ptr(&tramming_point_name[i]), []{ _menu_single_probe(MenuItemBase::itemIndex); });
+
+ ACTION_ITEM(MSG_BUTTON_DONE, []{
+ probe.stow(); // Stow before exiting Tramming Wizard
+ ui.goto_previous_screen_no_defer();
+ });
+ END_MENU();
+}
+
+// Init the wizard and enter the submenu
+void goto_tramming_wizard() {
+ DEBUG_ECHOLNPAIR("Screen: goto_tramming_wizard", 1);
+ tram_index = 0;
+ ui.defer_status_screen();
+
+ // Inject G28, wait for homing to complete,
+ set_all_unhomed();
+ queue.inject_P(TERN(G28_L0_ENSURES_LEVELING_OFF, PSTR("G28L0"), G28_STR));
+
+ ui.goto_screen([]{
+ _lcd_draw_homing();
+ if (all_axes_homed())
+ ui.goto_screen(tramming_wizard_menu);
+ });
+}
+
+#endif // HAS_LCD_MENU && ASSISTED_TRAMMING_WIZARD
diff --git a/Marlin/src/lcd/menu/menu_tune.cpp b/Marlin/src/lcd/menu/menu_tune.cpp
new file mode 100644
index 0000000..0fbb57f
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_tune.cpp
@@ -0,0 +1,239 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Tune Menu
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_LCD_MENU
+
+#include "menu_item.h"
+#include "../../module/motion.h"
+#include "../../module/planner.h"
+#include "../../module/temperature.h"
+#include "../../MarlinCore.h"
+
+#if ENABLED(SINGLENOZZLE_STANDBY_TEMP)
+ #include "../../module/tool_change.h"
+#endif
+
+#if HAS_LEVELING
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+#if ENABLED(BABYSTEPPING)
+
+ #include "../../feature/babystep.h"
+ #include "../lcdprint.h"
+ #if HAS_MARLINUI_U8GLIB
+ #include "../dogm/marlinui_DOGM.h"
+ #endif
+
+ void _lcd_babystep(const AxisEnum axis, PGM_P const msg) {
+ if (ui.use_click()) return ui.goto_previous_screen_no_defer();
+ if (ui.encoderPosition) {
+ const int16_t steps = int16_t(ui.encoderPosition) * (
+ #if ENABLED(BABYSTEP_XY)
+ axis == X_AXIS ? BABYSTEP_SIZE_X :
+ axis == Y_AXIS ? BABYSTEP_SIZE_Y :
+ #endif
+ BABYSTEP_SIZE_Z
+ );
+ ui.encoderPosition = 0;
+ ui.refresh(LCDVIEW_REDRAW_NOW);
+ babystep.add_steps(axis, steps);
+ }
+ if (ui.should_draw()) {
+ const float spm = planner.steps_to_mm[axis];
+ MenuEditItemBase::draw_edit_screen(msg, BABYSTEP_TO_STR(spm * babystep.accum));
+ #if ENABLED(BABYSTEP_DISPLAY_TOTAL)
+ const bool in_view = TERN1(HAS_MARLINUI_U8GLIB, PAGE_CONTAINS(LCD_PIXEL_HEIGHT - MENU_FONT_HEIGHT, LCD_PIXEL_HEIGHT - 1));
+ if (in_view) {
+ TERN_(HAS_MARLINUI_U8GLIB, ui.set_font(FONT_MENU));
+ #if ENABLED(TFT_COLOR_UI)
+ lcd_moveto(4, 3);
+ lcd_put_u8str_P(GET_TEXT(MSG_BABYSTEP_TOTAL));
+ lcd_put_wchar(':');
+ lcd_moveto(10, 3);
+ #else
+ lcd_moveto(0, TERN(HAS_MARLINUI_U8GLIB, LCD_PIXEL_HEIGHT - MENU_FONT_DESCENT, LCD_HEIGHT - 1));
+ lcd_put_u8str_P(GET_TEXT(MSG_BABYSTEP_TOTAL));
+ lcd_put_wchar(':');
+ #endif
+ lcd_put_u8str(BABYSTEP_TO_STR(spm * babystep.axis_total[BS_TOTAL_IND(axis)]));
+ }
+ #endif
+ }
+ }
+
+ inline void _lcd_babystep_go(const screenFunc_t screen) {
+ ui.goto_screen(screen);
+ ui.defer_status_screen();
+ babystep.accum = 0;
+ }
+
+ #if ENABLED(BABYSTEP_XY)
+ void _lcd_babystep_x() { _lcd_babystep(X_AXIS, GET_TEXT(MSG_BABYSTEP_X)); }
+ void _lcd_babystep_y() { _lcd_babystep(Y_AXIS, GET_TEXT(MSG_BABYSTEP_Y)); }
+ #endif
+
+ #if DISABLED(BABYSTEP_ZPROBE_OFFSET)
+ void _lcd_babystep_z() { _lcd_babystep(Z_AXIS, GET_TEXT(MSG_BABYSTEP_Z)); }
+ void lcd_babystep_z() { _lcd_babystep_go(_lcd_babystep_z); }
+ #endif
+
+#endif // BABYSTEPPING
+
+void menu_tune() {
+ START_MENU();
+ BACK_ITEM(MSG_MAIN);
+
+ //
+ // Speed:
+ //
+ EDIT_ITEM(int3, MSG_SPEED, &feedrate_percentage, 10, 999);
+
+ //
+ // Manual bed leveling, Bed Z:
+ //
+ #if BOTH(MESH_BED_LEVELING, LCD_BED_LEVELING)
+ EDIT_ITEM(float43, MSG_BED_Z, &mbl.z_offset, -1, 1);
+ #endif
+
+ //
+ // Nozzle:
+ // Nozzle [1-4]:
+ //
+ #if HOTENDS == 1
+ EDIT_ITEM_FAST(int3, MSG_NOZZLE, &thermalManager.temp_hotend[0].target, 0, HEATER_0_MAXTEMP - HOTEND_OVERSHOOT, []{ thermalManager.start_watching_hotend(0); });
+ #elif HAS_MULTI_HOTEND
+ HOTEND_LOOP()
+ EDIT_ITEM_FAST_N(int3, e, MSG_NOZZLE_N, &thermalManager.temp_hotend[e].target, 0, thermalManager.heater_maxtemp[e] - HOTEND_OVERSHOOT, []{ thermalManager.start_watching_hotend(MenuItemBase::itemIndex); });
+ #endif
+
+ #if ENABLED(SINGLENOZZLE_STANDBY_TEMP)
+ LOOP_S_L_N(e, 1, EXTRUDERS)
+ EDIT_ITEM_FAST_N(uint16_3, e, MSG_NOZZLE_STANDBY, &thermalManager.singlenozzle_temp[e], 0, thermalManager.heater_maxtemp[0] - HOTEND_OVERSHOOT);
+ #endif
+
+ //
+ // Bed:
+ //
+ #if HAS_HEATED_BED
+ EDIT_ITEM_FAST(int3, MSG_BED, &thermalManager.temp_bed.target, 0, BED_MAX_TARGET, thermalManager.start_watching_bed);
+ #endif
+
+ //
+ // Fan Speed:
+ //
+ #if HAS_FAN
+
+ DEFINE_SINGLENOZZLE_ITEM();
+
+ #if HAS_FAN0
+ _FAN_EDIT_ITEMS(0,FIRST_FAN_SPEED);
+ #endif
+ #if HAS_FAN1
+ FAN_EDIT_ITEMS(1);
+ #elif SNFAN(1)
+ singlenozzle_item(1);
+ #endif
+ #if HAS_FAN2
+ FAN_EDIT_ITEMS(2);
+ #elif SNFAN(2)
+ singlenozzle_item(2);
+ #endif
+ #if HAS_FAN3
+ FAN_EDIT_ITEMS(3);
+ #elif SNFAN(3)
+ singlenozzle_item(3);
+ #endif
+ #if HAS_FAN4
+ FAN_EDIT_ITEMS(4);
+ #elif SNFAN(4)
+ singlenozzle_item(4);
+ #endif
+ #if HAS_FAN5
+ FAN_EDIT_ITEMS(5);
+ #elif SNFAN(5)
+ singlenozzle_item(5);
+ #endif
+ #if HAS_FAN6
+ FAN_EDIT_ITEMS(6);
+ #elif SNFAN(6)
+ singlenozzle_item(6);
+ #endif
+ #if HAS_FAN7
+ FAN_EDIT_ITEMS(7);
+ #elif SNFAN(7)
+ singlenozzle_item(7);
+ #endif
+
+ #endif // HAS_FAN
+
+ //
+ // Flow:
+ //
+ #if EXTRUDERS
+ EDIT_ITEM(int3, MSG_FLOW, &planner.flow_percentage[active_extruder], 10, 999, []{ planner.refresh_e_factor(active_extruder); });
+ // Flow En:
+ #if HAS_MULTI_EXTRUDER
+ LOOP_L_N(n, EXTRUDERS)
+ EDIT_ITEM_N(int3, n, MSG_FLOW_N, &planner.flow_percentage[n], 10, 999, []{ planner.refresh_e_factor(MenuItemBase::itemIndex); });
+ #endif
+ #endif
+
+ //
+ // Advance K:
+ //
+ #if ENABLED(LIN_ADVANCE) && DISABLED(SLIM_LCD_MENUS)
+ #if EXTRUDERS == 1
+ EDIT_ITEM(float42_52, MSG_ADVANCE_K, &planner.extruder_advance_K[0], 0, 10);
+ #elif HAS_MULTI_EXTRUDER
+ LOOP_L_N(n, EXTRUDERS)
+ EDIT_ITEM_N(float42_52, n, MSG_ADVANCE_K_E, &planner.extruder_advance_K[n], 0, 10);
+ #endif
+ #endif
+
+ //
+ // Babystep X:
+ // Babystep Y:
+ // Babystep Z:
+ //
+ #if ENABLED(BABYSTEPPING)
+ #if ENABLED(BABYSTEP_XY)
+ SUBMENU(MSG_BABYSTEP_X, []{ _lcd_babystep_go(_lcd_babystep_x); });
+ SUBMENU(MSG_BABYSTEP_Y, []{ _lcd_babystep_go(_lcd_babystep_y); });
+ #endif
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ SUBMENU(MSG_ZPROBE_ZOFFSET, lcd_babystep_zoffset);
+ #else
+ SUBMENU(MSG_BABYSTEP_Z, lcd_babystep_z);
+ #endif
+ #endif
+
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU
diff --git a/Marlin/src/lcd/menu/menu_ubl.cpp b/Marlin/src/lcd/menu/menu_ubl.cpp
new file mode 100644
index 0000000..95e64a0
--- /dev/null
+++ b/Marlin/src/lcd/menu/menu_ubl.cpp
@@ -0,0 +1,639 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// Unified Bed Leveling Menus
+//
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if BOTH(HAS_LCD_MENU, AUTO_BED_LEVELING_UBL)
+
+#include "menu_item.h"
+#include "../../gcode/gcode.h"
+#include "../../gcode/queue.h"
+#include "../../module/motion.h"
+#include "../../module/planner.h"
+#include "../../module/settings.h"
+#include "../../feature/bedlevel/bedlevel.h"
+
+static int16_t ubl_storage_slot = 0,
+ custom_hotend_temp = 190,
+ side_points = 3,
+ ubl_fillin_amount = 5,
+ ubl_height_amount = 1;
+
+static uint8_t n_edit_pts = 1;
+static int8_t x_plot = 0, y_plot = 0; // May be negative during move
+
+#if HAS_HEATED_BED
+ static int16_t custom_bed_temp = 50;
+#endif
+
+float mesh_edit_accumulator; // Rounded to 2.5 decimal places on use
+
+inline float rounded_mesh_value() {
+ const int32_t rounded = int32_t(mesh_edit_accumulator * 1000);
+ return float(rounded - (rounded % 5L)) / 1000;
+}
+
+static void _lcd_mesh_fine_tune(PGM_P const msg) {
+ ui.defer_status_screen();
+ if (ubl.encoder_diff) {
+ mesh_edit_accumulator += TERN(IS_TFTGLCD_PANEL,
+ ubl.encoder_diff * 0.005f / ENCODER_PULSES_PER_STEP,
+ ubl.encoder_diff > 0 ? 0.005f : -0.005f
+ );
+ ubl.encoder_diff = 0;
+ IF_DISABLED(IS_TFTGLCD_PANEL, ui.refresh(LCDVIEW_CALL_REDRAW_NEXT));
+ }
+ TERN_(IS_TFTGLCD_PANEL, ui.refresh(LCDVIEW_CALL_REDRAW_NEXT));
+
+ if (ui.should_draw()) {
+ const float rounded_f = rounded_mesh_value();
+ MenuEditItemBase::draw_edit_screen(msg, ftostr43sign(rounded_f));
+ TERN_(MESH_EDIT_GFX_OVERLAY, _lcd_zoffset_overlay_gfx(rounded_f));
+ TERN_(HAS_GRAPHICAL_TFT, ui.refresh(LCDVIEW_NONE));
+ }
+}
+
+//
+// Called external to the menu system to acquire the result of an edit.
+//
+float lcd_mesh_edit() { return rounded_mesh_value(); }
+
+void lcd_mesh_edit_setup(const float &initial) {
+ TERN_(HAS_GRAPHICAL_TFT, ui.clear_lcd());
+ mesh_edit_accumulator = initial;
+ ui.goto_screen([]{ _lcd_mesh_fine_tune(GET_TEXT(MSG_MESH_EDIT_Z)); });
+}
+
+void _lcd_z_offset_edit() {
+ _lcd_mesh_fine_tune(GET_TEXT(MSG_UBL_Z_OFFSET));
+}
+
+float lcd_z_offset_edit() {
+ ui.goto_screen(_lcd_z_offset_edit);
+ return rounded_mesh_value();
+}
+
+void lcd_z_offset_edit_setup(const float &initial) {
+ mesh_edit_accumulator = initial;
+ ui.goto_screen(_lcd_z_offset_edit);
+}
+
+/**
+ * UBL Build Custom Mesh Command
+ */
+void _lcd_ubl_build_custom_mesh() {
+ char ubl_lcd_gcode[64];
+ #if HAS_HEATED_BED
+ sprintf_P(ubl_lcd_gcode, PSTR("G28\nM190 S%i\nM109 S%i\nG29 P1"), custom_bed_temp, custom_hotend_temp);
+ #else
+ sprintf_P(ubl_lcd_gcode, PSTR("G28\nM109 S%i\nG29 P1"), custom_hotend_temp);
+ #endif
+ queue.inject(ubl_lcd_gcode);
+}
+
+/**
+ * UBL Custom Mesh submenu
+ *
+ * << Build Mesh
+ * Hotend Temp: ---
+ * Bed Temp: ---
+ * Build Custom Mesh
+ */
+void _lcd_ubl_custom_mesh() {
+ START_MENU();
+ BACK_ITEM(MSG_UBL_BUILD_MESH_MENU);
+ #if HAS_HOTEND
+ EDIT_ITEM(int3, MSG_UBL_HOTEND_TEMP_CUSTOM, &custom_hotend_temp, EXTRUDE_MINTEMP, HEATER_0_MAXTEMP - HOTEND_OVERSHOOT);
+ #endif
+ #if HAS_HEATED_BED
+ EDIT_ITEM(int3, MSG_UBL_BED_TEMP_CUSTOM, &custom_bed_temp, BED_MINTEMP, BED_MAX_TARGET);
+ #endif
+ ACTION_ITEM(MSG_UBL_BUILD_CUSTOM_MESH, _lcd_ubl_build_custom_mesh);
+ END_MENU();
+}
+
+/**
+ * UBL Adjust Mesh Height Command
+ */
+void _lcd_ubl_adjust_height_cmd() {
+ char ubl_lcd_gcode[13];
+ const int ind = ubl_height_amount > 0 ? 6 : 7;
+ strcpy_P(ubl_lcd_gcode, PSTR("G29P6C-"));
+ sprintf_P(&ubl_lcd_gcode[ind], PSTR(".%i"), ABS(ubl_height_amount));
+ queue.inject(ubl_lcd_gcode);
+}
+
+/**
+ * UBL Adjust Mesh Height submenu
+ *
+ * << Edit Mesh
+ * Height Amount: ---
+ * Adjust Mesh Height
+ * << Info Screen
+ */
+void _menu_ubl_height_adjust() {
+ START_MENU();
+ BACK_ITEM(MSG_EDIT_MESH);
+ EDIT_ITEM(int3, MSG_UBL_MESH_HEIGHT_AMOUNT, &ubl_height_amount, -9, 9, _lcd_ubl_adjust_height_cmd);
+ ACTION_ITEM(MSG_INFO_SCREEN, ui.return_to_status);
+ END_MENU();
+}
+
+/**
+ * UBL Edit Mesh submenu
+ *
+ * << UBL Tools
+ * Fine Tune All
+ * Fine Tune Closest
+ * - Adjust Mesh Height >>
+ * << Info Screen
+ */
+void _lcd_ubl_edit_mesh() {
+ START_MENU();
+ BACK_ITEM(MSG_UBL_TOOLS);
+ GCODES_ITEM(MSG_UBL_FINE_TUNE_ALL, PSTR("G29P4R999T"));
+ GCODES_ITEM(MSG_UBL_FINE_TUNE_CLOSEST, PSTR("G29P4T"));
+ SUBMENU(MSG_UBL_MESH_HEIGHT_ADJUST, _menu_ubl_height_adjust);
+ ACTION_ITEM(MSG_INFO_SCREEN, ui.return_to_status);
+ END_MENU();
+}
+
+#if ENABLED(G26_MESH_VALIDATION)
+
+ /**
+ * UBL Validate Custom Mesh Command
+ */
+ void _lcd_ubl_validate_custom_mesh() {
+ char ubl_lcd_gcode[20];
+ sprintf_P(ubl_lcd_gcode, PSTR("G28\nG26CPH%" PRIi16 TERN_(HAS_HEATED_BED, "B%" PRIi16))
+ , custom_hotend_temp
+ #if HAS_HEATED_BED
+ , custom_bed_temp
+ #endif
+ );
+ queue.inject(ubl_lcd_gcode);
+ }
+
+ /**
+ * UBL Validate Mesh submenu
+ *
+ * << UBL Tools
+ * Mesh Validation with Material 1 up to 5
+ * Validate Custom Mesh
+ * << Info Screen
+ */
+ void _lcd_ubl_validate_mesh() {
+ START_MENU();
+ BACK_ITEM(MSG_UBL_TOOLS);
+ #if PREHEAT_COUNT
+ #if HAS_HEATED_BED
+ #define VALIDATE_MESH_GCODE_ITEM(M) \
+ GCODES_ITEM_N_S(M, ui.get_preheat_label(M), MSG_UBL_VALIDATE_MESH_M, PSTR("G28\nG26CPI" STRINGIFY(M)))
+ #else
+ #define VALIDATE_MESH_GCODE_ITEM(M) \
+ GCODES_ITEM_N_S(M, ui.get_preheat_label(M), MSG_UBL_VALIDATE_MESH_M, PSTR("G28\nG26CPB0I" STRINGIFY(M)))
+ #endif
+
+ VALIDATE_MESH_GCODE_ITEM(0);
+ #if PREHEAT_COUNT > 1
+ VALIDATE_MESH_GCODE_ITEM(1);
+ #if PREHEAT_COUNT > 2
+ VALIDATE_MESH_GCODE_ITEM(2);
+ #if PREHEAT_COUNT > 3
+ VALIDATE_MESH_GCODE_ITEM(3);
+ #if PREHEAT_COUNT > 4
+ VALIDATE_MESH_GCODE_ITEM(4);
+ #endif
+ #endif
+ #endif
+ #endif
+ #endif // PREHEAT_COUNT
+ ACTION_ITEM(MSG_UBL_VALIDATE_CUSTOM_MESH, _lcd_ubl_validate_custom_mesh);
+ ACTION_ITEM(MSG_INFO_SCREEN, ui.return_to_status);
+ END_MENU();
+ }
+
+#endif
+
+/**
+ * UBL Grid Leveling submenu
+ *
+ * << UBL Tools
+ * Side points: ---
+ * Level Mesh
+ */
+void _lcd_ubl_grid_level() {
+ START_MENU();
+ BACK_ITEM(MSG_UBL_TOOLS);
+ EDIT_ITEM(int3, MSG_UBL_SIDE_POINTS, &side_points, 2, 6);
+ ACTION_ITEM(MSG_UBL_MESH_LEVEL, []{
+ char ubl_lcd_gcode[12];
+ sprintf_P(ubl_lcd_gcode, PSTR("G29J%i"), side_points);
+ queue.inject(ubl_lcd_gcode);
+ });
+ END_MENU();
+}
+
+/**
+ * UBL Mesh Leveling submenu
+ *
+ * << UBL Tools
+ * 3-Point Mesh Leveling
+ * - Grid Mesh Leveling >>
+ * << Info Screen
+ */
+void _lcd_ubl_mesh_leveling() {
+ START_MENU();
+ BACK_ITEM(MSG_UBL_TOOLS);
+ GCODES_ITEM(MSG_UBL_3POINT_MESH_LEVELING, PSTR("G29J0"));
+ SUBMENU(MSG_UBL_GRID_MESH_LEVELING, _lcd_ubl_grid_level);
+ ACTION_ITEM(MSG_INFO_SCREEN, ui.return_to_status);
+ END_MENU();
+}
+
+/**
+ * UBL Fill-in Amount Mesh Command
+ */
+void _lcd_ubl_fillin_amount_cmd() {
+ char ubl_lcd_gcode[18];
+ sprintf_P(ubl_lcd_gcode, PSTR("G29P3RC.%i"), ubl_fillin_amount);
+ gcode.process_subcommands_now(ubl_lcd_gcode);
+}
+
+/**
+ * UBL Fill-in Mesh submenu
+ *
+ * << Build Mesh
+ * Fill-in Amount: ---
+ * Fill-in Mesh
+ * Smart Fill-in
+ * Manual Fill-in
+ * << Info Screen
+ */
+void _menu_ubl_fillin() {
+ START_MENU();
+ BACK_ITEM(MSG_UBL_BUILD_MESH_MENU);
+ EDIT_ITEM(int3, MSG_UBL_FILLIN_AMOUNT, &ubl_fillin_amount, 0, 9, _lcd_ubl_fillin_amount_cmd);
+ GCODES_ITEM(MSG_UBL_SMART_FILLIN, PSTR("G29P3T0"));
+ GCODES_ITEM(MSG_UBL_MANUAL_FILLIN, PSTR("G29P2BT0"));
+ ACTION_ITEM(MSG_INFO_SCREEN, ui.return_to_status);
+ END_MENU();
+}
+
+void _lcd_ubl_invalidate() {
+ ubl.invalidate();
+ SERIAL_ECHOLNPGM("Mesh invalidated.");
+}
+
+/**
+ * UBL Build Mesh submenu
+ *
+ * << UBL Tools
+ * Build Mesh with Material 1 up to 5
+ * - Build Custom Mesh >>
+ * Build Cold Mesh
+ * - Fill-in Mesh >>
+ * Continue Bed Mesh
+ * Invalidate All
+ * Invalidate Closest
+ * << Info Screen
+ */
+void _lcd_ubl_build_mesh() {
+ START_MENU();
+ BACK_ITEM(MSG_UBL_TOOLS);
+ #if PREHEAT_COUNT
+ #if HAS_HEATED_BED
+ #define PREHEAT_BED_GCODE(M) "M190I" STRINGIFY(M) "\n"
+ #else
+ #define PREHEAT_BED_GCODE(M) ""
+ #endif
+ #define BUILD_MESH_GCODE_ITEM(M) GCODES_ITEM_S(ui.get_preheat_label(M), MSG_UBL_BUILD_MESH_M, \
+ PSTR( \
+ "G28\n" \
+ PREHEAT_BED_GCODE(M) \
+ "M109I" STRINGIFY(M) "\n" \
+ "G29P1\n" \
+ "M104S0\n" \
+ "M140S0" \
+ ) )
+ BUILD_MESH_GCODE_ITEM(0);
+ #if PREHEAT_COUNT > 1
+ BUILD_MESH_GCODE_ITEM(1);
+ #if PREHEAT_COUNT > 2
+ BUILD_MESH_GCODE_ITEM(2);
+ #if PREHEAT_COUNT > 3
+ BUILD_MESH_GCODE_ITEM(3);
+ #if PREHEAT_COUNT > 4
+ BUILD_MESH_GCODE_ITEM(4);
+ #endif
+ #endif
+ #endif
+ #endif
+ #endif // PREHEAT_COUNT
+
+ SUBMENU(MSG_UBL_BUILD_CUSTOM_MESH, _lcd_ubl_custom_mesh);
+ GCODES_ITEM(MSG_UBL_BUILD_COLD_MESH, PSTR("G29NP1"));
+ SUBMENU(MSG_UBL_FILLIN_MESH, _menu_ubl_fillin);
+ GCODES_ITEM(MSG_UBL_CONTINUE_MESH, PSTR("G29P1C"));
+ ACTION_ITEM(MSG_UBL_INVALIDATE_ALL, _lcd_ubl_invalidate);
+ GCODES_ITEM(MSG_UBL_INVALIDATE_CLOSEST, PSTR("G29I"));
+ ACTION_ITEM(MSG_INFO_SCREEN, ui.return_to_status);
+ END_MENU();
+}
+
+/**
+ * UBL Load / Save Mesh Commands
+ */
+inline void _lcd_ubl_load_save_cmd(const char loadsave, PGM_P const msg) {
+ char ubl_lcd_gcode[40];
+ sprintf_P(ubl_lcd_gcode, PSTR("G29%c%i\nM117 "), loadsave, ubl_storage_slot);
+ sprintf_P(&ubl_lcd_gcode[strlen(ubl_lcd_gcode)], msg, ubl_storage_slot);
+ gcode.process_subcommands_now(ubl_lcd_gcode);
+}
+void _lcd_ubl_load_mesh_cmd() { _lcd_ubl_load_save_cmd('L', GET_TEXT(MSG_MESH_LOADED)); }
+void _lcd_ubl_save_mesh_cmd() { _lcd_ubl_load_save_cmd('S', GET_TEXT(MSG_MESH_SAVED)); }
+
+/**
+ * UBL Mesh Storage submenu
+ *
+ * << Unified Bed Leveling
+ * Memory Slot: ---
+ * Load Bed Mesh
+ * Save Bed Mesh
+ */
+void _lcd_ubl_storage_mesh() {
+ int16_t a = settings.calc_num_meshes();
+ START_MENU();
+ BACK_ITEM(MSG_UBL_LEVEL_BED);
+ if (!WITHIN(ubl_storage_slot, 0, a - 1))
+ STATIC_ITEM(MSG_UBL_NO_STORAGE);
+ else {
+ EDIT_ITEM(int3, MSG_UBL_STORAGE_SLOT, &ubl_storage_slot, 0, a - 1);
+ ACTION_ITEM(MSG_UBL_LOAD_MESH, _lcd_ubl_load_mesh_cmd);
+ ACTION_ITEM(MSG_UBL_SAVE_MESH, _lcd_ubl_save_mesh_cmd);
+ }
+ END_MENU();
+}
+
+/**
+ * UBL LCD "radar" map point editing
+ */
+void _lcd_ubl_map_edit_cmd() {
+ char ubl_lcd_gcode[50], str[10], str2[10];
+ dtostrf(ubl.mesh_index_to_xpos(x_plot), 0, 2, str);
+ dtostrf(ubl.mesh_index_to_ypos(y_plot), 0, 2, str2);
+ snprintf_P(ubl_lcd_gcode, sizeof(ubl_lcd_gcode), PSTR("G29P4X%sY%sR%i"), str, str2, int(n_edit_pts));
+ queue.inject(ubl_lcd_gcode);
+}
+
+/**
+ * UBL LCD Map Movement
+ */
+void ubl_map_move_to_xy() {
+ const xy_pos_t xy = { ubl.mesh_index_to_xpos(x_plot), ubl.mesh_index_to_ypos(y_plot) };
+
+ // Some printers have unreachable areas in the mesh. Skip the move if unreachable.
+ if (!position_is_reachable(xy)) return;
+
+ #if ENABLED(DELTA)
+ if (current_position.z > delta_clip_start_height) { // Make sure the delta has fully free motion
+ destination = current_position;
+ destination.z = delta_clip_start_height;
+ prepare_internal_fast_move_to_destination(homing_feedrate(Z_AXIS)); // Set current_position from destination
+ }
+ #endif
+
+ // Use the built-in manual move handler to move to the mesh point.
+ ui.manual_move.set_destination(xy);
+ ui.manual_move.soon(ALL_AXES);
+}
+
+inline int32_t grid_index(const uint8_t x, const uint8_t y) {
+ return (GRID_MAX_POINTS_X) * y + x;
+}
+
+/**
+ * UBL LCD "radar" map
+ */
+void ubl_map_screen() {
+ // static millis_t next_move = 0;
+ // const millis_t ms = millis();
+
+ uint8_t x, y;
+
+ if (ui.first_page) {
+
+ // On click send "G29 P4 ..." to edit the Z value
+ if (ui.use_click()) {
+ _lcd_ubl_map_edit_cmd();
+ return;
+ }
+
+ ui.defer_status_screen();
+
+ #if IS_KINEMATIC
+ // Index of the mesh point upon entry
+ const int32_t old_pos_index = grid_index(x_plot, y_plot);
+ // Direction from new (unconstrained) encoder value
+ const int8_t step_dir = int32_t(ui.encoderPosition) < old_pos_index ? -1 : 1;
+ #endif
+
+ do {
+ // Now, keep the encoder position within range
+ if (int32_t(ui.encoderPosition) < 0) ui.encoderPosition = GRID_MAX_POINTS + TERN(TOUCH_SCREEN, ui.encoderPosition, -1);
+ if (int32_t(ui.encoderPosition) > GRID_MAX_POINTS - 1) ui.encoderPosition = TERN(TOUCH_SCREEN, ui.encoderPosition - GRID_MAX_POINTS, 0);
+
+ // Draw the grid point based on the encoder
+ x = ui.encoderPosition % (GRID_MAX_POINTS_X);
+ y = ui.encoderPosition / (GRID_MAX_POINTS_X);
+
+ // Validate if needed
+ #if IS_KINEMATIC
+ const xy_pos_t xy = { ubl.mesh_index_to_xpos(x), ubl.mesh_index_to_ypos(y) };
+ if (position_is_reachable(xy)) break; // Found a valid point
+ ui.encoderPosition += step_dir; // Test the next point
+ #endif
+ } while(ENABLED(IS_KINEMATIC));
+
+ // Determine number of points to edit
+ #if IS_KINEMATIC
+ n_edit_pts = 9; // TODO: Delta accessible edit points
+ #else
+ const bool xc = WITHIN(x, 1, GRID_MAX_POINTS_X - 2),
+ yc = WITHIN(y, 1, GRID_MAX_POINTS_Y - 2);
+ n_edit_pts = yc ? (xc ? 9 : 6) : (xc ? 6 : 4); // Corners
+ #endif
+
+ // Refresh is also set by encoder movement
+ //if (int32_t(ui.encoderPosition) != grid_index(x, y))
+ // ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
+ }
+
+ // Draw the grid point based on the encoder
+ x = ui.encoderPosition % (GRID_MAX_POINTS_X);
+ y = ui.encoderPosition / (GRID_MAX_POINTS_X);
+
+ if (ui.should_draw()) ui.ubl_plot(x, y);
+
+ // Add a move if needed to match the grid point
+ if (x != x_plot || y != y_plot) {
+ x_plot = x; y_plot = y; // The move is always posted, so update the grid point now
+ ubl_map_move_to_xy(); // Sets up a "manual move"
+ ui.refresh(LCDVIEW_CALL_REDRAW_NEXT); // Clean up a half drawn box
+ }
+}
+
+/**
+ * UBL LCD "radar" map homing
+ */
+void _ubl_map_screen_homing() {
+ ui.defer_status_screen();
+ _lcd_draw_homing();
+ if (all_axes_homed()) {
+ ubl.lcd_map_control = true; // Return to the map screen after editing Z
+ ui.goto_screen(ubl_map_screen, grid_index(x_plot, y_plot)); // Pre-set the encoder value
+ ui.manual_move.menu_scale = 0; // Immediate move
+ ubl_map_move_to_xy(); // Move to current mesh point
+ ui.manual_move.menu_scale = 1; // Delayed moves
+ }
+}
+
+/**
+ * UBL Homing before LCD map
+ */
+void _ubl_goto_map_screen() {
+ if (planner.movesplanned()) return; // The ACTION_ITEM will do nothing
+ if (!all_axes_trusted()) {
+ set_all_unhomed();
+ queue.inject_P(G28_STR);
+ }
+ ui.goto_screen(_ubl_map_screen_homing); // Go to the "Homing" screen
+}
+
+/**
+ * UBL Output map submenu
+ *
+ * << Unified Bed Leveling
+ * Output for Host
+ * Output for CSV
+ * Off Printer Backup
+ */
+void _lcd_ubl_output_map() {
+ START_MENU();
+ BACK_ITEM(MSG_UBL_LEVEL_BED);
+ GCODES_ITEM(MSG_UBL_OUTPUT_MAP_HOST, PSTR("G29T0"));
+ GCODES_ITEM(MSG_UBL_OUTPUT_MAP_CSV, PSTR("G29T1"));
+ GCODES_ITEM(MSG_UBL_OUTPUT_MAP_BACKUP, PSTR("G29S-1"));
+ END_MENU();
+}
+
+/**
+ * UBL Tools submenu
+ *
+ * << Unified Bed Leveling
+ * - Build Mesh >>
+ * - Validate Mesh >>
+ * - Edit Mesh >>
+ * - Mesh Leveling >>
+ */
+void _menu_ubl_tools() {
+ START_MENU();
+ BACK_ITEM(MSG_UBL_LEVEL_BED);
+ SUBMENU(MSG_UBL_BUILD_MESH_MENU, _lcd_ubl_build_mesh);
+ GCODES_ITEM(MSG_UBL_MANUAL_MESH, PSTR("G29I999\nG29P2BT0"));
+ #if ENABLED(G26_MESH_VALIDATION)
+ SUBMENU(MSG_UBL_VALIDATE_MESH_MENU, _lcd_ubl_validate_mesh);
+ #endif
+ SUBMENU(MSG_EDIT_MESH, _lcd_ubl_edit_mesh);
+ SUBMENU(MSG_UBL_MESH_LEVELING, _lcd_ubl_mesh_leveling);
+ END_MENU();
+}
+
+#if ENABLED(G26_MESH_VALIDATION)
+
+ /**
+ * UBL Step-By-Step submenu
+ *
+ * << Unified Bed Leveling
+ * 1 Build Cold Mesh
+ * 2 Smart Fill-in
+ * - 3 Validate Mesh >>
+ * 4 Fine Tune All
+ * - 5 Validate Mesh >>
+ * 6 Fine Tune All
+ * 7 Save Bed Mesh
+ */
+ void _lcd_ubl_step_by_step() {
+ START_MENU();
+ BACK_ITEM(MSG_UBL_LEVEL_BED);
+ GCODES_ITEM(MSG_UBL_1_BUILD_COLD_MESH, PSTR("G29NP1"));
+ GCODES_ITEM(MSG_UBL_2_SMART_FILLIN, PSTR("G29P3T0"));
+ SUBMENU(MSG_UBL_3_VALIDATE_MESH_MENU, _lcd_ubl_validate_mesh);
+ GCODES_ITEM(MSG_UBL_4_FINE_TUNE_ALL, PSTR("G29P4R999T"));
+ SUBMENU(MSG_UBL_5_VALIDATE_MESH_MENU, _lcd_ubl_validate_mesh);
+ GCODES_ITEM(MSG_UBL_6_FINE_TUNE_ALL, PSTR("G29P4R999T"));
+ ACTION_ITEM(MSG_UBL_7_SAVE_MESH, _lcd_ubl_save_mesh_cmd);
+ END_MENU();
+ }
+
+#endif
+
+/**
+ * UBL System submenu
+ *
+ * << Motion
+ * - Manually Build Mesh >>
+ * - Activate UBL >>
+ * - Deactivate UBL >>
+ * - Step-By-Step UBL >>
+ * - Mesh Storage >>
+ * - Output Map >>
+ * - UBL Tools >>
+ * - Output UBL Info >>
+ */
+void _lcd_ubl_level_bed() {
+ START_MENU();
+ BACK_ITEM(MSG_MOTION);
+ if (planner.leveling_active)
+ GCODES_ITEM(MSG_UBL_DEACTIVATE_MESH, PSTR("G29D"));
+ else
+ GCODES_ITEM(MSG_UBL_ACTIVATE_MESH, PSTR("G29A"));
+ #if ENABLED(G26_MESH_VALIDATION)
+ SUBMENU(MSG_UBL_STEP_BY_STEP_MENU, _lcd_ubl_step_by_step);
+ #endif
+ ACTION_ITEM(MSG_UBL_MESH_EDIT, _ubl_goto_map_screen);
+ SUBMENU(MSG_UBL_STORAGE_MESH_MENU, _lcd_ubl_storage_mesh);
+ SUBMENU(MSG_UBL_OUTPUT_MAP, _lcd_ubl_output_map);
+ SUBMENU(MSG_UBL_TOOLS, _menu_ubl_tools);
+ GCODES_ITEM(MSG_UBL_INFO_UBL, PSTR("G29W"));
+ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
+ editable.decimal = planner.z_fade_height;
+ EDIT_ITEM_FAST(float3, MSG_Z_FADE_HEIGHT, &editable.decimal, 0, 100, []{ set_z_fade_height(editable.decimal); });
+ #endif
+ END_MENU();
+}
+
+#endif // HAS_LCD_MENU && AUTO_BED_LEVELING_UBL