diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h
index a424b1967e..6a96411863 100644
--- a/Marlin/Configuration.h
+++ b/Marlin/Configuration.h
@@ -2756,6 +2756,11 @@
//
//#define DWIN_CREALITY_LCD
+//
+// Ender-3 v2 OEM display, enhanced.
+//
+//#define DWIN_CREALITY_LCD_ENHANCED
+
//
// Ender-3 v2 OEM display with enhancements by Jacob Myers
//
diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp
index d68e87eb89..49db8c61b9 100644
--- a/Marlin/src/MarlinCore.cpp
+++ b/Marlin/src/MarlinCore.cpp
@@ -77,6 +77,9 @@
#if ENABLED(DWIN_CREALITY_LCD)
#include "lcd/e3v2/creality/dwin.h"
#include "lcd/e3v2/creality/rotary_encoder.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "lcd/e3v2/enhanced/dwin.h"
+ #include "lcd/e3v2/enhanced/rotary_encoder.h"
#elif ENABLED(DWIN_CREALITY_LCD_JYERSUI)
#include "lcd/e3v2/jyersui/dwin.h"
#include "lcd/e3v2/jyersui/rotary_encoder.h"
@@ -849,7 +852,7 @@ void idle(bool no_stepper_sleep/*=false*/) {
TERN_(USE_BEEPER, buzzer.tick());
// Handle UI input / draw events
- TERN(DWIN_CREALITY_LCD, DWIN_Update(), ui.update());
+ TERN(HAS_DWIN_E3V2_BASIC, DWIN_Update(), ui.update());
// Run i2c Position Encoders
#if ENABLED(I2C_POSITION_ENCODERS)
@@ -904,7 +907,7 @@ void kill(PGM_P const lcd_error/*=nullptr*/, PGM_P const lcd_component/*=nullptr
// Echo the LCD message to serial for extra context
if (lcd_error) { SERIAL_ECHO_START(); SERIAL_ECHOLNPGM_P(lcd_error); }
- #if HAS_DISPLAY
+ #if EITHER(HAS_DISPLAY, DWIN_CREALITY_LCD_ENHANCED)
ui.kill_screen(lcd_error ?: GET_TEXT(MSG_KILLED), lcd_component ?: NUL_STR);
#else
UNUSED(lcd_error); UNUSED(lcd_component);
@@ -1315,7 +1318,7 @@ void setup() {
// UI must be initialized before EEPROM
// (because EEPROM code calls the UI).
- #if ENABLED(DWIN_CREALITY_LCD)
+ #if HAS_DWIN_E3V2_BASIC
SETUP_RUN(DWIN_Startup());
#else
SETUP_RUN(ui.init());
@@ -1590,7 +1593,7 @@ void setup() {
SERIAL_ECHO_TERNARY(err, "BL24CXX Check ", "failed", "succeeded", "!\n");
#endif
- #if ENABLED(DWIN_CREALITY_LCD)
+ #if HAS_DWIN_E3V2_BASIC
Encoder_Configuration();
HMI_Init();
HMI_SetLanguageCache();
@@ -1598,7 +1601,7 @@ void setup() {
DWIN_StatusChanged_P(GET_TEXT(WELCOME_MSG));
#endif
- #if HAS_SERVICE_INTERVALS && DISABLED(DWIN_CREALITY_LCD)
+ #if HAS_SERVICE_INTERVALS && !HAS_DWIN_E3V2_BASIC
ui.reset_status(true); // Show service messages or keep current status
#endif
diff --git a/Marlin/src/feature/pause.cpp b/Marlin/src/feature/pause.cpp
index 9a402141e6..f1d6dbb985 100644
--- a/Marlin/src/feature/pause.cpp
+++ b/Marlin/src/feature/pause.cpp
@@ -53,6 +53,8 @@
#if ENABLED(EXTENSIBLE_UI)
#include "../lcd/extui/ui_api.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../lcd/e3v2/enhanced/dwin.h"
#endif
#include "../lcd/marlinui.h"
@@ -242,6 +244,7 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_FILAMENT_CHANGE_PURGE)));
TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, GET_TEXT(MSG_FILAMENT_CHANGE_PURGE), CONTINUE_STR));
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_Popup_Confirm(ICON_BLTouch, GET_TEXT(MSG_FILAMENT_CHANGE_PURGE), CONTINUE_STR));
wait_for_user = true; // A click or M108 breaks the purge_length loop
for (float purge_count = purge_length; purge_count > 0 && wait_for_user; --purge_count)
unscaled_e_move(1, ADVANCED_PAUSE_PURGE_FEEDRATE);
@@ -265,7 +268,7 @@ bool load_filament(const_float_t slow_load_length/*=0*/, const_float_t fast_load
// Show "Purge More" / "Resume" menu and wait for reply
KEEPALIVE_STATE(PAUSED_FOR_USER);
wait_for_user = false;
- #if HAS_LCD_MENU
+ #if EITHER(HAS_LCD_MENU, DWIN_CREALITY_LCD_ENHANCED)
ui.pause_show_message(PAUSE_MESSAGE_OPTION); // Also sets PAUSE_RESPONSE_WAIT_FOR
#else
pause_menu_response = PAUSE_RESPONSE_WAIT_FOR;
@@ -525,6 +528,8 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep
TERN_(EXTENSIBLE_UI, ExtUI::onStatusChanged_P(GET_TEXT(MSG_REHEATING)));
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, ui.set_status_P(GET_TEXT(MSG_REHEATING)));
+
// Re-enable the heaters if they timed out
HOTEND_LOOP() thermalManager.reset_hotend_idle_timer(e);
@@ -538,8 +543,13 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep
const millis_t nozzle_timeout = SEC_TO_MS(PAUSE_PARK_NOZZLE_TIMEOUT);
HOTEND_LOOP() thermalManager.heater_idle[e].start(nozzle_timeout);
+
TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, GET_TEXT(MSG_REHEATDONE), CONTINUE_STR));
+
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_REHEATDONE)));
+
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, ui.set_status_P(GET_TEXT(MSG_REHEATDONE)));
+
wait_for_user = true;
nozzle_timed_out = false;
@@ -675,6 +685,7 @@ void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_
TERN_(HAS_STATUS_MESSAGE, ui.reset_status());
TERN_(HAS_LCD_MENU, ui.return_to_status());
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, HMI_ReturnScreen());
}
#endif // ADVANCED_PAUSE_FEATURE
diff --git a/Marlin/src/feature/powerloss.cpp b/Marlin/src/feature/powerloss.cpp
index 3409e6714d..c86cb4f0d6 100644
--- a/Marlin/src/feature/powerloss.cpp
+++ b/Marlin/src/feature/powerloss.cpp
@@ -40,7 +40,7 @@ uint8_t PrintJobRecovery::queue_index_r;
uint32_t PrintJobRecovery::cmd_sdpos, // = 0
PrintJobRecovery::sdpos[BUFSIZE];
-#if ENABLED(DWIN_CREALITY_LCD)
+#if HAS_DWIN_E3V2_BASIC
bool PrintJobRecovery::dwin_flag; // = false
#endif
diff --git a/Marlin/src/feature/powerloss.h b/Marlin/src/feature/powerloss.h
index d3ecc6c9cc..6a13c92df7 100644
--- a/Marlin/src/feature/powerloss.h
+++ b/Marlin/src/feature/powerloss.h
@@ -145,7 +145,7 @@ class PrintJobRecovery {
static uint32_t cmd_sdpos, //!< SD position of the next command
sdpos[BUFSIZE]; //!< SD positions of queued commands
- #if ENABLED(DWIN_CREALITY_LCD)
+ #if HAS_DWIN_E3V2_BASIC
static bool dwin_flag;
#endif
diff --git a/Marlin/src/feature/runout.cpp b/Marlin/src/feature/runout.cpp
index 531ca1081f..ef1f876bdf 100644
--- a/Marlin/src/feature/runout.cpp
+++ b/Marlin/src/feature/runout.cpp
@@ -68,6 +68,8 @@ bool FilamentMonitorBase::enabled = true,
#if ENABLED(EXTENSIBLE_UI)
#include "../lcd/extui/ui_api.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../lcd/e3v2/enhanced/dwin.h"
#endif
void event_filament_runout(const uint8_t extruder) {
@@ -86,6 +88,7 @@ void event_filament_runout(const uint8_t extruder) {
#endif
TERN_(EXTENSIBLE_UI, ExtUI::onFilamentRunout(ExtUI::getTool(extruder)));
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_FilamentRunout(extruder));
#if ANY(HOST_PROMPT_SUPPORT, HOST_ACTION_COMMANDS, MULTI_FILAMENT_SENSOR)
const char tool = '0' + TERN0(MULTI_FILAMENT_SENSOR, extruder);
diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp
index ca36f6d46e..f756aa89df 100644
--- a/Marlin/src/gcode/bedlevel/abl/G29.cpp
+++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp
@@ -58,10 +58,10 @@
#if ENABLED(EXTENSIBLE_UI)
#include "../../../lcd/extui/ui_api.h"
-#endif
-
-#if ENABLED(DWIN_CREALITY_LCD)
+#elif ENABLED(DWIN_CREALITY_LCD)
#include "../../../lcd/e3v2/creality/dwin.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../../../lcd/e3v2/enhanced/dwin.h"
#endif
#if HAS_MULTI_HOTEND
@@ -403,10 +403,9 @@ G29_TYPE GcodeSuite::G29() {
#if ENABLED(AUTO_BED_LEVELING_3POINT)
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> 3-point Leveling");
points[0].z = points[1].z = points[2].z = 0; // Probe at 3 arbitrary points
- #endif
-
- #if BOTH(AUTO_BED_LEVELING_BILINEAR, EXTENSIBLE_UI)
- ExtUI::onMeshLevelingStart();
+ #elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
+ TERN_(EXTENSIBLE_UI, ExtUI::onMeshLevelingStart());
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_MeshLevelingStart());
#endif
if (!faux) {
@@ -886,9 +885,7 @@ G29_TYPE GcodeSuite::G29() {
process_subcommands_now_P(PSTR(Z_PROBE_END_SCRIPT));
#endif
- #if ENABLED(DWIN_CREALITY_LCD)
- DWIN_CompletedLeveling();
- #endif
+ TERN_(HAS_DWIN_E3V2_BASIC, DWIN_CompletedLeveling());
report_current_position();
diff --git a/Marlin/src/gcode/bedlevel/mbl/G29.cpp b/Marlin/src/gcode/bedlevel/mbl/G29.cpp
index b8ca8bdee5..adfe61d3d2 100644
--- a/Marlin/src/gcode/bedlevel/mbl/G29.cpp
+++ b/Marlin/src/gcode/bedlevel/mbl/G29.cpp
@@ -40,6 +40,8 @@
#if ENABLED(EXTENSIBLE_UI)
#include "../../../lcd/extui/ui_api.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../../../lcd/e3v2/enhanced/dwin.h"
#endif
#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
@@ -191,6 +193,7 @@ void GcodeSuite::G29() {
if (parser.seenval('Z')) {
mbl.z_values[ix][iy] = parser.value_linear_units();
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, mbl.z_values[ix][iy]));
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_MeshUpdate(ix, iy, mbl.z_values[ix][iy]));
}
else
return echo_not_entered('Z');
diff --git a/Marlin/src/gcode/bedlevel/ubl/M421.cpp b/Marlin/src/gcode/bedlevel/ubl/M421.cpp
index f1e1b76126..e6f0ef1f89 100644
--- a/Marlin/src/gcode/bedlevel/ubl/M421.cpp
+++ b/Marlin/src/gcode/bedlevel/ubl/M421.cpp
@@ -33,6 +33,8 @@
#if ENABLED(EXTENSIBLE_UI)
#include "../../../lcd/extui/ui_api.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../../../lcd/e3v2/enhanced/dwin.h"
#endif
/**
@@ -67,6 +69,7 @@ void GcodeSuite::M421() {
float &zval = ubl.z_values[ij.x][ij.y]; // Altering this Mesh Point
zval = hasN ? NAN : parser.value_linear_units() + (hasQ ? zval : 0); // N=NAN, Z=NEWVAL, or Q=ADDVAL
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ij.x, ij.y, zval)); // Ping ExtUI in case it's showing the mesh
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_MeshUpdate(ij.x, ij.y, zval));
}
}
diff --git a/Marlin/src/gcode/calibrate/G28.cpp b/Marlin/src/gcode/calibrate/G28.cpp
index 89ad20d906..d85c0306d4 100644
--- a/Marlin/src/gcode/calibrate/G28.cpp
+++ b/Marlin/src/gcode/calibrate/G28.cpp
@@ -46,12 +46,13 @@
#endif
#include "../../lcd/marlinui.h"
-#if ENABLED(DWIN_CREALITY_LCD)
- #include "../../lcd/e3v2/creality/dwin.h"
-#endif
#if ENABLED(EXTENSIBLE_UI)
#include "../../lcd/extui/ui_api.h"
+#elif ENABLED(DWIN_CREALITY_LCD)
+ #include "../../lcd/e3v2/creality/dwin.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../../lcd/e3v2/enhanced/dwin.h"
#endif
#if HAS_L64XX // set L6470 absolute position registers to counts
@@ -238,7 +239,7 @@ void GcodeSuite::G28() {
return;
}
- TERN_(DWIN_CREALITY_LCD, DWIN_StartHoming());
+ TERN_(HAS_DWIN_E3V2_BASIC, DWIN_StartHoming());
TERN_(EXTENSIBLE_UI, ExtUI::onHomingStart());
planner.synchronize(); // Wait for planner moves to finish!
@@ -522,7 +523,7 @@ void GcodeSuite::G28() {
ui.refresh();
- TERN_(DWIN_CREALITY_LCD, DWIN_CompletedHoming());
+ TERN_(HAS_DWIN_E3V2_BASIC, DWIN_CompletedHoming());
TERN_(EXTENSIBLE_UI, ExtUI::onHomingComplete());
report_current_position();
diff --git a/Marlin/src/gcode/control/M997.cpp b/Marlin/src/gcode/control/M997.cpp
index cdff96f1ac..73d795bcef 100644
--- a/Marlin/src/gcode/control/M997.cpp
+++ b/Marlin/src/gcode/control/M997.cpp
@@ -24,11 +24,17 @@
#if ENABLED(PLATFORM_M997_SUPPORT)
+#if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../../lcd/e3v2/enhanced/dwin.h"
+#endif
+
/**
* M997: Perform in-application firmware update
*/
void GcodeSuite::M997() {
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_RebootScreen());
+
flashFirmware(parser.intval('S'));
}
diff --git a/Marlin/src/gcode/feature/powerloss/M1000.cpp b/Marlin/src/gcode/feature/powerloss/M1000.cpp
index 0e731016dd..3ebb286b57 100644
--- a/Marlin/src/gcode/feature/powerloss/M1000.cpp
+++ b/Marlin/src/gcode/feature/powerloss/M1000.cpp
@@ -27,9 +27,14 @@
#include "../../gcode.h"
#include "../../../feature/powerloss.h"
#include "../../../module/motion.h"
+
#include "../../../lcd/marlinui.h"
#if ENABLED(EXTENSIBLE_UI)
#include "../../../lcd/extui/ui_api.h"
+#elif ENABLED(DWIN_CREALITY_LCD)
+ #include "../../../lcd/e3v2/creality/dwin.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../../../lcd/e3v2/enhanced/dwin.h"
#elif ENABLED(DWIN_CREALITY_LCD_JYERSUI)
#include "../../../lcd/e3v2/jyersui/dwin.h" // Temporary fix until it can be better implemented
#endif
@@ -64,7 +69,7 @@ void GcodeSuite::M1000() {
if (parser.seen_test('S')) {
#if HAS_LCD_MENU
ui.goto_screen(menu_job_recovery);
- #elif ENABLED(DWIN_CREALITY_LCD)
+ #elif HAS_DWIN_E3V2_BASIC
recovery.dwin_flag = true;
#elif ENABLED(DWIN_CREALITY_LCD_JYERSUI) // Temporary fix until it can be better implemented
CrealityDWIN.Popup_Handler(Resume);
diff --git a/Marlin/src/gcode/lcd/M0_M1.cpp b/Marlin/src/gcode/lcd/M0_M1.cpp
index 414c4ce023..cb37bfec24 100644
--- a/Marlin/src/gcode/lcd/M0_M1.cpp
+++ b/Marlin/src/gcode/lcd/M0_M1.cpp
@@ -35,6 +35,8 @@
#include "../../lcd/marlinui.h"
#elif ENABLED(EXTENSIBLE_UI)
#include "../../lcd/extui/ui_api.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../../lcd/e3v2/enhanced/dwin.h"
#endif
#if ENABLED(HOST_PROMPT_SUPPORT)
@@ -68,6 +70,8 @@ void GcodeSuite::M0_M1() {
ExtUI::onUserConfirmRequired(parser.string_arg); // Can this take an SRAM string??
else
ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_USERWAIT));
+ #elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ DWIN_Popup_Confirm(ICON_BLTouch, parser.string_arg ?: GET_TEXT(MSG_STOPPED), GET_TEXT(MSG_USERWAIT));
#else
if (parser.string_arg) {
diff --git a/Marlin/src/gcode/lcd/M73.cpp b/Marlin/src/gcode/lcd/M73.cpp
index 8996e5c88e..b7a9b3459e 100644
--- a/Marlin/src/gcode/lcd/M73.cpp
+++ b/Marlin/src/gcode/lcd/M73.cpp
@@ -28,6 +28,10 @@
#include "../../lcd/marlinui.h"
#include "../../sd/cardreader.h"
+#if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../../lcd/e3v2/enhanced/dwin.h"
+#endif
+
/**
* M73: Set percentage complete (for display on LCD)
*
@@ -35,13 +39,23 @@
* M73 P25 ; Set progress to 25%
*/
void GcodeSuite::M73() {
- if (parser.seenval('P'))
- ui.set_progress((PROGRESS_SCALE) > 1
- ? parser.value_float() * (PROGRESS_SCALE)
- : parser.value_byte()
- );
- #if ENABLED(USE_M73_REMAINING_TIME)
- if (parser.seenval('R')) ui.set_remaining_time(60 * parser.value_ulong());
+
+ #if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+
+ DWIN_Progress_Update();
+
+ #else
+
+ if (parser.seenval('P'))
+ ui.set_progress((PROGRESS_SCALE) > 1
+ ? parser.value_float() * (PROGRESS_SCALE)
+ : parser.value_byte()
+ );
+
+ #if ENABLED(USE_M73_REMAINING_TIME)
+ if (parser.seenval('R')) ui.set_remaining_time(60 * parser.value_ulong());
+ #endif
+
#endif
}
diff --git a/Marlin/src/gcode/sd/M1001.cpp b/Marlin/src/gcode/sd/M1001.cpp
index cd4933ff27..14bd712d27 100644
--- a/Marlin/src/gcode/sd/M1001.cpp
+++ b/Marlin/src/gcode/sd/M1001.cpp
@@ -48,6 +48,8 @@
#if ENABLED(EXTENSIBLE_UI)
#include "../../lcd/extui/ui_api.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../../lcd/e3v2/enhanced/dwin.h"
#endif
#if ENABLED(HOST_ACTION_COMMANDS)
@@ -106,6 +108,7 @@ void GcodeSuite::M1001() {
#endif
TERN_(EXTENSIBLE_UI, ExtUI::onPrintFinished());
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_Print_Finished());
// Re-select the last printed file in the UI
TERN_(SD_REPRINT_LAST_SELECTED_FILE, ui.reselect_last_file());
diff --git a/Marlin/src/gcode/stats/M75-M78.cpp b/Marlin/src/gcode/stats/M75-M78.cpp
index 66f9f8eb8d..b55409946e 100644
--- a/Marlin/src/gcode/stats/M75-M78.cpp
+++ b/Marlin/src/gcode/stats/M75-M78.cpp
@@ -29,11 +29,19 @@
#include "../../MarlinCore.h" // for startOrResumeJob
+#if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../../lcd/e3v2/enhanced/dwin.h"
+#endif
+
/**
* M75: Start print timer
*/
void GcodeSuite::M75() {
startOrResumeJob();
+ #if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ DWIN_Print_Header(parser.string_arg && parser.string_arg[0] ? parser.string_arg : GET_TEXT(MSG_HOST_START_PRINT));
+ DWIN_Print_Started(false);
+ #endif
}
/**
@@ -49,29 +57,30 @@ void GcodeSuite::M76() {
*/
void GcodeSuite::M77() {
print_job_timer.stop();
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_Print_Finished());
}
#if ENABLED(PRINTCOUNTER)
-/**
+ /**
* M78: Show print statistics
*/
-void GcodeSuite::M78() {
- if (parser.intval('S') == 78) { // "M78 S78" will reset the statistics
+ void GcodeSuite::M78() {
+ if (parser.intval('S') == 78) { // "M78 S78" will reset the statistics
print_job_timer.initStats();
ui.reset_status();
- return;
+ return;
}
#if HAS_SERVICE_INTERVALS
- if (parser.seenval('R')) {
+ if (parser.seenval('R')) {
print_job_timer.resetServiceInterval(parser.value_int());
ui.reset_status();
- return;
+ return;
}
#endif
print_job_timer.showStats();
-}
+ }
#endif // PRINTCOUNTER
diff --git a/Marlin/src/gcode/temp/M303.cpp b/Marlin/src/gcode/temp/M303.cpp
index ad3afe6e46..0d0ce478ee 100644
--- a/Marlin/src/gcode/temp/M303.cpp
+++ b/Marlin/src/gcode/temp/M303.cpp
@@ -30,6 +30,8 @@
#if ENABLED(EXTENSIBLE_UI)
#include "../../lcd/extui/ui_api.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../../lcd/e3v2/enhanced/dwin.h"
#endif
/**
@@ -71,6 +73,7 @@ void GcodeSuite::M303() {
default:
SERIAL_ECHOLNPGM(STR_PID_BAD_HEATER_ID);
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_BAD_EXTRUDER_NUM));
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_PidTuning(PID_BAD_EXTRUDER_NUM));
return;
}
diff --git a/Marlin/src/inc/Conditionals_LCD.h b/Marlin/src/inc/Conditionals_LCD.h
index 825ad58f9a..a7bde803ba 100644
--- a/Marlin/src/inc/Conditionals_LCD.h
+++ b/Marlin/src/inc/Conditionals_LCD.h
@@ -493,7 +493,10 @@
#endif
// Aliases for LCD features
-#if ANY(DWIN_CREALITY_LCD, DWIN_CREALITY_LCD_JYERSUI)
+#if EITHER(DWIN_CREALITY_LCD, DWIN_CREALITY_LCD_ENHANCED)
+ #define HAS_DWIN_E3V2_BASIC 1
+#endif
+#if EITHER(HAS_DWIN_E3V2_BASIC, DWIN_CREALITY_LCD_JYERSUI)
#define HAS_DWIN_E3V2 1
#endif
diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h
index 6de8dc9860..61a9ea2c9a 100644
--- a/Marlin/src/inc/Conditionals_post.h
+++ b/Marlin/src/inc/Conditionals_post.h
@@ -421,7 +421,7 @@
#endif
#endif
-#if ENABLED(DWIN_CREALITY_LCD_JYERSUI)
+#if EITHER(DWIN_CREALITY_LCD_ENHANCED, DWIN_CREALITY_LCD_JYERSUI)
#define HAS_LCD_BRIGHTNESS 1
#endif
@@ -2953,7 +2953,7 @@
* Advanced Pause - Filament Change
*/
#if ENABLED(ADVANCED_PAUSE_FEATURE)
- #if ANY(HAS_LCD_MENU, EXTENSIBLE_UI, DWIN_CREALITY_LCD_JYERSUI) || BOTH(EMERGENCY_PARSER, HOST_PROMPT_SUPPORT)
+ #if ANY(HAS_LCD_MENU, EXTENSIBLE_UI, DWIN_CREALITY_LCD_ENHANCED, DWIN_CREALITY_LCD_JYERSUI) || BOTH(EMERGENCY_PARSER, HOST_PROMPT_SUPPORT)
#define M600_PURGE_MORE_RESUMABLE 1
#endif
#ifndef FILAMENT_CHANGE_SLOW_LOAD_LENGTH
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index 59b4a68ff1..3c00d84142 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -808,7 +808,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
#error "PROGRESS_MSG_EXPIRE must be greater than or equal to 0."
#endif
#elif ENABLED(LCD_SET_PROGRESS_MANUALLY) && NONE(HAS_MARLINUI_U8GLIB, HAS_GRAPHICAL_TFT, HAS_MARLINUI_HD44780, EXTENSIBLE_UI, HAS_DWIN_E3V2, IS_DWIN_MARLINUI)
- #error "LCD_SET_PROGRESS_MANUALLY requires LCD_PROGRESS_BAR, Character LCD, Graphical LCD, TFT, DWIN_CREALITY_LCD, DWIN_CREALITY_LCD_JYERSUI, DWIN_MARLINUI_*, or EXTENSIBLE_UI."
+ #error "LCD_SET_PROGRESS_MANUALLY requires LCD_PROGRESS_BAR, Character LCD, Graphical LCD, TFT, DWIN_CREALITY_LCD, DWIN_CREALITY_LCD_ENHANCED, DWIN_CREALITY_LCD_JYERSUI, DWIN_MARLINUI_*, OR EXTENSIBLE_UI."
#endif
#if ENABLED(USE_M73_REMAINING_TIME) && DISABLED(LCD_SET_PROGRESS_MANUALLY)
@@ -1748,7 +1748,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
* LCD_BED_LEVELING requirements
*/
#if ENABLED(LCD_BED_LEVELING)
- #if NONE(HAS_LCD_MENU, DWIN_CREALITY_LCD)
+ #if NONE(HAS_LCD_MENU, DWIN_CREALITY_LCD, DWIN_CREALITY_LCD_ENHANCED)
#error "LCD_BED_LEVELING is not supported by the selected LCD controller."
#elif !(ENABLED(MESH_BED_LEVELING) || HAS_ABL_NOT_UBL)
#error "LCD_BED_LEVELING requires MESH_BED_LEVELING or AUTO_BED_LEVELING."
@@ -2655,7 +2655,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
+ COUNT_ENABLED(ANYCUBIC_LCD_I3MEGA, ANYCUBIC_LCD_CHIRON, ANYCUBIC_TFT35) \
+ COUNT_ENABLED(DGUS_LCD_UI_ORIGIN, DGUS_LCD_UI_FYSETC, DGUS_LCD_UI_HIPRECY, DGUS_LCD_UI_MKS, DGUS_LCD_UI_RELOADED) \
+ COUNT_ENABLED(ENDER2_STOCKDISPLAY, CR10_STOCKDISPLAY) \
- + COUNT_ENABLED(DWIN_CREALITY_LCD, DWIN_CREALITY_LCD_JYERSUI, DWIN_MARLINUI_PORTRAIT, DWIN_MARLINUI_LANDSCAPE) \
+ + COUNT_ENABLED(DWIN_CREALITY_LCD, DWIN_CREALITY_LCD_ENHANCED, DWIN_CREALITY_LCD_JYERSUI, DWIN_MARLINUI_PORTRAIT, DWIN_MARLINUI_LANDSCAPE) \
+ COUNT_ENABLED(FYSETC_MINI_12864_X_X, FYSETC_MINI_12864_1_2, FYSETC_MINI_12864_2_0, FYSETC_MINI_12864_2_1, FYSETC_GENERIC_12864_1_1) \
+ COUNT_ENABLED(LCD_SAINSMART_I2C_1602, LCD_SAINSMART_I2C_2004) \
+ COUNT_ENABLED(MKS_12864OLED, MKS_12864OLED_SSD1306) \
@@ -2763,6 +2763,18 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
#elif BOTH(LCD_BED_LEVELING, PROBE_MANUALLY)
#error "DWIN_CREALITY_LCD does not support LCD_BED_LEVELING with PROBE_MANUALLY."
#endif
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #if DISABLED(SDSUPPORT)
+ #error "DWIN_CREALITY_LCD_ENHANCED requires SDSUPPORT to be enabled."
+ #elif ENABLED(PID_EDIT_MENU)
+ #error "DWIN_CREALITY_LCD_ENHANCED does not support PID_EDIT_MENU."
+ #elif ENABLED(PID_AUTOTUNE_MENU)
+ #error "DWIN_CREALITY_LCD_ENHANCED does not support PID_AUTOTUNE_MENU."
+ #elif ENABLED(LEVEL_BED_CORNERS)
+ #error "DWIN_CREALITY_LCD_ENHANCED does not support LEVEL_BED_CORNERS."
+ #elif BOTH(LCD_BED_LEVELING, PROBE_MANUALLY)
+ #error "DWIN_CREALITY_LCD_ENHANCED does not support LCD_BED_LEVELING with PROBE_MANUALLY."
+ #endif
#endif
/**
diff --git a/Marlin/src/lcd/e3v2/creality/README.md b/Marlin/src/lcd/e3v2/README.md
similarity index 100%
rename from Marlin/src/lcd/e3v2/creality/README.md
rename to Marlin/src/lcd/e3v2/README.md
diff --git a/Marlin/src/lcd/e3v2/creality/rotary_encoder.h b/Marlin/src/lcd/e3v2/creality/rotary_encoder.h
index f73577b3b0..48e13b7ece 100644
--- a/Marlin/src/lcd/e3v2/creality/rotary_encoder.h
+++ b/Marlin/src/lcd/e3v2/creality/rotary_encoder.h
@@ -22,12 +22,12 @@
#pragma once
/*****************************************************************************
- * @file lcd/e3v2/creality/rotary_encoder.h
- * @author LEO / Creality3D
- * @date 2019/07/06
- * @version 2.0.1
- * @brief Rotary encoder functions
- ****************************************************************************/
+ * @file lcd/e3v2/creality/rotary_encoder.h
+ * @author LEO / Creality3D
+ * @date 2019/07/06
+ * @version 2.0.1
+ * @brief Rotary encoder functions
+ ****************************************************************************/
#include "../../../inc/MarlinConfig.h"
diff --git a/Marlin/src/lcd/e3v2/enhanced/dwin.cpp b/Marlin/src/lcd/e3v2/enhanced/dwin.cpp
new file mode 100644
index 0000000000..0143d62bd2
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/enhanced/dwin.cpp
@@ -0,0 +1,3657 @@
+/**
+ * DWIN UI Enhanced implementation
+ * Author: Miguel A. Risco-Castillo
+ * Version: 3.6.1
+ * Date: 2021/08/29
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser 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 Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+
+#include "dwin.h"
+
+#include "../../fontutils.h"
+#include "../../marlinui.h"
+
+#include "../../../sd/cardreader.h"
+
+#include "../../../MarlinCore.h"
+#include "../../../core/serial.h"
+#include "../../../core/macros.h"
+
+#include "../../../module/temperature.h"
+#include "../../../module/printcounter.h"
+#include "../../../module/motion.h"
+#include "../../../module/planner.h"
+
+#include "../../../gcode/gcode.h"
+#include "../../../gcode/queue.h"
+
+#if HAS_FILAMENT_SENSOR
+ #include "../../../feature/runout.h"
+#endif
+
+#if ENABLED(EEPROM_SETTINGS)
+ #include "../../../module/settings.h"
+#endif
+
+#if ENABLED(HOST_ACTION_COMMANDS)
+ #include "../../../feature/host_actions.h"
+#endif
+
+#if HAS_ONESTEP_LEVELING
+ #include "../../../feature/bedlevel/bedlevel.h"
+#endif
+
+#if HAS_BED_PROBE
+ #include "../../../module/probe.h"
+#endif
+
+#if EITHER(BABYSTEP_ZPROBE_OFFSET, JUST_BABYSTEP)
+ #include "../../../feature/babystep.h"
+#endif
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+ #include "../../../feature/powerloss.h"
+#endif
+
+#include
+#include
+#include
+
+#ifndef MACHINE_SIZE
+ #define MACHINE_SIZE STRINGIFY(X_BED_SIZE) "x" STRINGIFY(Y_BED_SIZE) "x" STRINGIFY(Z_MAX_POS)
+#endif
+
+#include "lockscreen.h"
+
+#ifndef CORP_WEBSITE
+ #define CORP_WEBSITE WEBSITE_URL
+#endif
+
+#define PAUSE_HEAT
+
+#define USE_STRING_HEADINGS
+#define USE_STRING_TITLES
+
+#define MENU_CHAR_LIMIT 24
+
+// Print speed limit
+#define MIN_PRINT_SPEED 10
+#define MAX_PRINT_SPEED 999
+
+// Print flow limit
+#define MIN_PRINT_FLOW 10
+#define MAX_PRINT_FLOW 299
+
+// Load and Unload limits
+#define MAX_LOAD_UNLOAD 500
+
+// Feedspeed limit (max feedspeed = DEFAULT_MAX_FEEDRATE * 2)
+#define MIN_MAXFEEDSPEED 1
+#define MIN_MAXACCELERATION 1
+#define MIN_MAXJERK 0.1
+#define MIN_STEP 1
+#define MAX_STEP 999.9
+
+// Extruder's temperature limits
+#define MIN_ETEMP HEATER_0_MINTEMP
+#define MAX_ETEMP (HEATER_0_MAXTEMP - HOTEND_OVERSHOOT)
+
+#define FEEDRATE_E (60)
+
+// Minimum unit (0.1) : multiple (10)
+#define UNITFDIGITS 1
+#define MINUNITMULT POW(10, UNITFDIGITS)
+
+#define ENCODER_WAIT_MS 20
+#define DWIN_VAR_UPDATE_INTERVAL 1024
+#define DWIN_SCROLL_UPDATE_INTERVAL SEC_TO_MS(2)
+#define DWIN_REMAIN_TIME_UPDATE_INTERVAL SEC_TO_MS(20)
+
+#define BABY_Z_VAR TERN(HAS_BED_PROBE, probe.offset.z, dwin_zoffset)
+
+// Structs
+HMI_value_t HMI_value;
+HMI_flag_t HMI_flag{0};
+HMI_data_t HMI_data;
+
+millis_t dwin_heat_time = 0;
+
+uint8_t checkkey = MainMenu;
+uint8_t last_checkkey = MainMenu;
+
+typedef struct {
+ uint8_t now, last;
+ void set(uint8_t v) { now = last = v; }
+ void reset() { set(0); }
+ bool changed() { bool c = (now != last); if (c) last = now; return c; }
+ bool dec() { if (now) now--; return changed(); }
+ bool inc(uint8_t v) { if (now < (v - 1)) now++; else now = (v - 1); return changed(); }
+} select_t;
+
+select_t select_page{0}, select_file{0}, select_print{0};
+uint8_t index_file = MROWS;
+
+bool dwin_abort_flag = false; // Flag to reset feedrate, return to Home
+
+constexpr float default_max_feedrate[] = DEFAULT_MAX_FEEDRATE;
+constexpr float default_max_acceleration[] = DEFAULT_MAX_ACCELERATION;
+
+#if HAS_CLASSIC_JERK
+ constexpr float default_max_jerk[] = { DEFAULT_XJERK, DEFAULT_YJERK, DEFAULT_ZJERK, DEFAULT_EJERK };
+#endif
+
+static uint8_t _percent_done = 0;
+static uint32_t _remain_time = 0;
+
+// Additional Aux Host Support
+static bool sdprint = false;
+
+#if ENABLED(PAUSE_HEAT)
+ #if HAS_HOTEND
+ celsius_t resume_hotend_temp = 0;
+ #endif
+ #if HAS_HEATED_BED
+ celsius_t resume_bed_temp = 0;
+ #endif
+ #if HAS_FAN
+ uint16_t resume_fan = 0;
+ #endif
+#endif
+
+#if HAS_ZOFFSET_ITEM
+ float dwin_zoffset = 0, last_zoffset = 0;
+#endif
+
+#if HAS_HOTEND
+ float last_E = 0;
+#endif
+
+// New menu system pointers
+MenuClass *PrepareMenu = nullptr;
+MenuClass *LevBedMenu = nullptr;
+MenuClass *MoveMenu = nullptr;
+MenuClass *ControlMenu = nullptr;
+MenuClass *AdvancedSettings = nullptr;
+#if HAS_HOME_OFFSET
+ MenuClass *HomeOffMenu = nullptr;
+#endif
+#if HAS_BED_PROBE
+ MenuClass *ProbeSetMenu = nullptr;
+#endif
+MenuClass *FilSetMenu = nullptr;
+MenuClass *SelectColorMenu = nullptr;
+MenuClass *GetColorMenu = nullptr;
+MenuClass *TuneMenu = nullptr;
+MenuClass *MotionMenu = nullptr;
+MenuClass *FilamentMenu = nullptr;
+#if ENABLED(MESH_BED_LEVELING)
+ MenuClass *ManualMesh = nullptr;
+#endif
+#if HAS_HOTEND
+ MenuClass *PreheatMenu = nullptr;
+#endif
+MenuClass *TemperatureMenu = nullptr;
+MenuClass *MaxSpeedMenu = nullptr;
+MenuClass *MaxAccelMenu = nullptr;
+MenuClass *MaxJerkMenu = nullptr;
+MenuClass *StepsMenu = nullptr;
+MenuClass *HotendPIDMenu = nullptr;
+MenuClass *BedPIDMenu = nullptr;
+#if EITHER(HAS_BED_PROBE, BABYSTEPPING)
+ MenuClass *ZOffsetWizMenu = nullptr;
+#endif
+
+// Updatable menuitems pointers
+MenuItemClass *HotendTargetItem = nullptr;
+MenuItemClass *BedTargetItem = nullptr;
+MenuItemClass *FanSpeedItem = nullptr;
+MenuItemClass *MMeshMoveZItem = nullptr;
+
+#define DWIN_LANGUAGE_EEPROM_ADDRESS 0x01 // Between 0x01 and 0x63 (EEPROM_OFFSET-1)
+ // BL24CXX::check() uses 0x00
+
+inline bool HMI_IsChinese() { return HMI_flag.language == DWIN_CHINESE; }
+
+void HMI_SetLanguageCache() {
+ DWIN_JPG_CacheTo1(HMI_IsChinese() ? Language_Chinese : Language_English);
+}
+
+void HMI_SetLanguage() {
+ #if BOTH(EEPROM_SETTINGS, IIC_BL24CXX_EEPROM)
+ BL24CXX::read(DWIN_LANGUAGE_EEPROM_ADDRESS, (uint8_t*)&HMI_flag.language, sizeof(HMI_flag.language));
+ #endif
+ HMI_SetLanguageCache();
+}
+
+void HMI_ToggleLanguage() {
+ HMI_flag.language = HMI_IsChinese() ? DWIN_ENGLISH : DWIN_CHINESE;
+ HMI_SetLanguageCache();
+ #if BOTH(EEPROM_SETTINGS, IIC_BL24CXX_EEPROM)
+ BL24CXX::write(DWIN_LANGUAGE_EEPROM_ADDRESS, (uint8_t*)&HMI_flag.language, sizeof(HMI_flag.language));
+ #endif
+}
+
+typedef struct { uint16_t x, y[2], w, h; } text_info_t;
+
+void ICON_Button(const bool here, const int iconid, const frame_rect_t &ico, const text_info_t (&txt)[2]) {
+ const bool cn = HMI_IsChinese();
+ DWIN_ICON_Show(1, 0, 0, ICON, iconid + here, ico.x, ico.y);
+ if (here) DWIN_Draw_Rectangle(0, HMI_data.Highlight_Color, ico.x, ico.y, ico.x + ico.w - 1, ico.y + ico.h - 1);
+ DWIN_Frame_AreaCopy(1, txt[cn].x, txt[cn].y[here], txt[cn].x + txt[cn].w - 1, txt[cn].y[here] + txt[cn].h - 1, ico.x + (ico.w - txt[cn].w) / 2, (ico.y + ico.h - 28) - txt[cn].h/2);
+}
+
+//
+// Main Menu: "Print"
+//
+void ICON_Print() {
+ constexpr frame_rect_t ico = { 17, 110, 110, 100 };
+ constexpr text_info_t txt[2] = {
+ { 1, { 417, 449 }, 30, 14 },
+ { 1, { 405, 447 }, 27, 15 }
+ };
+ ICON_Button(select_page.now == 0, ICON_Print_0, ico, txt);
+}
+
+//
+// Main Menu: "Prepare"
+//
+void ICON_Prepare() {
+ constexpr frame_rect_t ico = { 145, 110, 110, 100 };
+ constexpr text_info_t txt[2] = {
+ { 33, { 417, 449 }, 51, 14 },
+ { 31, { 405, 447 }, 27, 15 }
+ };
+ ICON_Button(select_page.now == 1, ICON_Prepare_0, ico, txt);
+}
+
+//
+// Main Menu: "Control"
+//
+void ICON_Control() {
+ constexpr frame_rect_t ico = { 17, 226, 110, 100 };
+ constexpr text_info_t txt[2] = {
+ { 85, { 417, 449 }, 46, 14 },
+ { 61, { 405, 447 }, 27, 15 }
+ };
+ ICON_Button(select_page.now == 2, ICON_Control_0, ico, txt);
+}
+
+//
+// Main Menu: "Info"
+//
+void ICON_StartInfo() {
+ constexpr frame_rect_t ico = { 145, 226, 110, 100 };
+ constexpr text_info_t txt[2] = {
+ { 133, { 417, 449 }, 23, 14 },
+ { 91, { 405, 447 }, 27, 15 }
+ };
+ ICON_Button(select_page.now == 3, ICON_Info_0, ico, txt);
+}
+
+//
+// Main Menu: "Level"
+//
+void ICON_Leveling() {
+ constexpr frame_rect_t ico = { 145, 226, 110, 100 };
+ constexpr text_info_t txt[2] = {
+ { 88, { 433, 464 }, 36, 14 },
+ { 211, { 405, 447 }, 27, 15 }
+ };
+ ICON_Button(select_page.now == 3, ICON_Leveling_0, ico, txt);
+}
+
+//
+// Printing: "Tune"
+//
+void ICON_Tune() {
+ constexpr frame_rect_t ico = { 8, 232, 80, 100 };
+ constexpr text_info_t txt[2] = {
+ { 0, { 433, 464 }, 32, 14 },
+ { 121, { 405, 447 }, 27, 15 }
+ };
+ ICON_Button(select_print.now == 0, ICON_Setup_0, ico, txt);
+}
+
+//
+// Printing: "Pause"
+//
+void ICON_Pause() {
+ constexpr frame_rect_t ico = { 96, 232, 80, 100 };
+ constexpr text_info_t txt[2] = {
+ { 157, { 417, 449 }, 39, 14 },
+ { 181, { 405, 447 }, 27, 15 }
+ };
+ ICON_Button(select_print.now == 1, ICON_Pause_0, ico, txt);
+}
+
+//
+// Printing: "Resume"
+//
+void ICON_Resume() {
+ constexpr frame_rect_t ico = { 96, 232, 80, 100 };
+ constexpr text_info_t txt[2] = {
+ { 33, { 433, 464 }, 53, 14 },
+ { 1, { 405, 447 }, 27, 15 }
+ };
+ ICON_Button(select_print.now == 1, ICON_Continue_0, ico, txt);
+}
+
+//
+// Printing: "Stop"
+//
+void ICON_Stop() {
+ constexpr frame_rect_t ico = { 184, 232, 80, 100 };
+ constexpr text_info_t txt[2] = {
+ { 196, { 417, 449 }, 29, 14 },
+ { 151, { 405, 447 }, 27, 12 }
+ };
+ ICON_Button(select_print.now == 2, ICON_Stop_0, ico, txt);
+}
+
+void Draw_Menu_Cursor(const uint8_t line) {
+ DWIN_Draw_Rectangle(1, HMI_data.Cursor_color, 0, MBASE(line) - 18, 14, MBASE(line + 1) - 20);
+}
+
+void Erase_Menu_Cursor(const uint8_t line) {
+ DWIN_Draw_Rectangle(1, HMI_data.Background_Color, 0, MBASE(line) - 18, 14, MBASE(line + 1) - 20);
+}
+
+void Move_Highlight(const int16_t from, const uint16_t newline) {
+ Erase_Menu_Cursor(newline - from);
+ Draw_Menu_Cursor(newline);
+}
+
+void Add_Menu_Line() {
+ Move_Highlight(1, MROWS);
+ DWIN_Draw_Line(HMI_data.SplitLine_Color, 16, MBASE(MROWS + 1) - 20, 256, MBASE(MROWS + 1) - 19);
+}
+
+void Scroll_Menu(const uint8_t dir) {
+ DWIN_Frame_AreaMove(1, dir, MLINE, HMI_data.Background_Color, 0, 31, DWIN_WIDTH, 349);
+ switch (dir) {
+ case DWIN_SCROLL_DOWN: Move_Highlight(-1, 0); break;
+ case DWIN_SCROLL_UP: Add_Menu_Line(); break;
+ }
+}
+
+inline uint16_t nr_sd_menu_items() {
+ return card.get_num_Files() + !card.flag.workDirIsRoot;
+}
+
+void Erase_Menu_Text(const uint8_t line) {
+ DWIN_Draw_Rectangle(1, HMI_data.Background_Color, LBLX, MBASE(line) - 14, 271, MBASE(line) + 28);
+}
+
+void Draw_Menu_Line(const uint8_t line, const uint8_t icon=0, const char * const label=nullptr, bool more=false) {
+ if (label) DWINUI::Draw_String(LBLX, MBASE(line) - 1, (char*)label);
+ if (icon) DWINUI::Draw_Icon(icon, 26, MBASE(line) - 3);
+ if (more) DWINUI::Draw_Icon(ICON_More, 226, MBASE(line) - 3);
+ DWIN_Draw_Line(HMI_data.SplitLine_Color, 16, MBASE(line) + 33, 256, MBASE(line) + 33);
+}
+
+void Draw_Chkb_Line(const uint8_t line, const bool checked) {
+ DWINUI::Draw_Checkbox(HMI_data.Text_Color, HMI_data.Background_Color, VALX + 16, MBASE(line) - 1, checked);
+}
+
+void Draw_Menu_IntValue(uint16_t bcolor, const uint8_t line, uint8_t iNum, const uint16_t value=0) {
+ DWINUI::Draw_Int(HMI_data.Text_Color, bcolor, iNum , VALX, MBASE(line) - 1, value);
+}
+
+// The "Back" label is always on the first line
+void Draw_Back_Label() {
+ if (HMI_IsChinese())
+ DWIN_Frame_AreaCopy(1, 129, 72, 156, 84, LBLX, MBASE(0));
+ else
+ DWIN_Frame_AreaCopy(1, 223, 179, 254, 189, LBLX, MBASE(0));
+}
+
+// Draw "Back" line at the top
+void Draw_Back_First(const bool is_sel=true) {
+ Draw_Menu_Line(0, ICON_Back);
+ Draw_Back_Label();
+ if (is_sel) Draw_Menu_Cursor(0);
+}
+
+inline ENCODER_DiffState get_encoder_state() {
+ static millis_t Encoder_ms = 0;
+ const millis_t ms = millis();
+ if (PENDING(ms, Encoder_ms)) return ENCODER_DIFF_NO;
+ const ENCODER_DiffState state = Encoder_ReceiveAnalyze();
+ if (state != ENCODER_DIFF_NO) Encoder_ms = ms + ENCODER_WAIT_MS;
+ return state;
+}
+
+template
+inline bool Apply_Encoder(const ENCODER_DiffState &encoder_diffState, T &valref) {
+ if (encoder_diffState == ENCODER_DIFF_CW)
+ valref += EncoderRate.encoderMoveValue;
+ else if (encoder_diffState == ENCODER_DIFF_CCW)
+ valref -= EncoderRate.encoderMoveValue;
+ return encoder_diffState == ENCODER_DIFF_ENTER;
+}
+
+//
+// Draw Popup Windows
+//
+
+inline void Draw_Popup_Bkgd_60() {
+ DWIN_Draw_Rectangle(1, HMI_data.PopupBg_color, 14, 60, 258, 330);
+ DWIN_Draw_Rectangle(0, HMI_data.Highlight_Color, 14, 60, 258, 330);
+}
+
+inline void Draw_Popup_Bkgd_105() {
+ DWIN_Draw_Rectangle(1, HMI_data.PopupBg_color, 14, 105, 258, 374);
+ DWIN_Draw_Rectangle(0, HMI_data.Highlight_Color, 14, 105, 258, 374);
+}
+
+void Clear_Popup_Area() {
+ DWIN_Draw_Rectangle(1, HMI_data.Background_Color, 0, 31, DWIN_WIDTH, DWIN_HEIGHT);
+}
+
+void DWIN_Draw_Popup(uint8_t icon=0, const char * const msg1=nullptr, const char * const msg2=nullptr, uint8_t button=0) {
+ DWINUI::ClearMenuArea();
+ Draw_Popup_Bkgd_60();
+ if (icon) DWINUI::Draw_Icon(icon, 101, 105);
+ if (msg1) DWINUI::Draw_CenteredString(HMI_data.PopupTxt_Color, 210, msg1);
+ if (msg2) DWINUI::Draw_CenteredString(HMI_data.PopupTxt_Color, 240, msg2);
+ if (button) DWINUI::Draw_Icon(button, 86, 280);
+}
+
+void DWIN_Popup_Confirm(uint8_t icon, const char * const msg1, const char * const msg2) {
+ HMI_SaveProcessID(WaitResponse);
+ DWIN_Draw_Popup(icon, msg1, msg2, ICON_Confirm_E); // Button Confirm
+ DWIN_UpdateLCD();
+}
+
+void DWIN_Popup_Continue(uint8_t icon, const char * const msg1, const char * const msg2) {
+ HMI_SaveProcessID(WaitResponse);
+ DWIN_Draw_Popup(icon, msg1, msg2, ICON_Continue_E); // Button Continue
+ DWIN_UpdateLCD();
+}
+
+#if HAS_HOTEND
+
+ void Popup_Window_ETempTooLow() {
+ if (HMI_IsChinese()) {
+ HMI_SaveProcessID(WaitResponse);
+ DWINUI::ClearMenuArea();
+ Draw_Popup_Bkgd_60();
+ DWINUI::Draw_Icon(ICON_TempTooLow, 102, 105);
+ DWIN_Frame_AreaCopy(1, 103, 371, 136, 386, 69, 240);
+ DWIN_Frame_AreaCopy(1, 170, 371, 270, 386, 102, 240);
+ DWINUI::Draw_Icon(ICON_Confirm_C, 86, 280);
+ DWIN_UpdateLCD();
+ }
+ else
+ DWIN_Popup_Confirm(ICON_TempTooLow, "Nozzle is too cold", "Preheat the hotend");
+ }
+
+#endif
+
+void Popup_Window_Resume() {
+ Clear_Popup_Area();
+ Draw_Popup_Bkgd_105();
+ if (HMI_IsChinese()) {
+ DWIN_Frame_AreaCopy(1, 160, 338, 235, 354, 98, 135);
+ DWIN_Frame_AreaCopy(1, 103, 321, 271, 335, 52, 192);
+ DWINUI::Draw_Icon(ICON_Cancel_C, 26, 307);
+ DWINUI::Draw_Icon(ICON_Continue_C, 146, 307);
+ }
+ else {
+ DWINUI::Draw_CenteredString(HMI_data.PopupTxt_Color, 115, F("Continue Print"));
+ DWINUI::Draw_CenteredString(HMI_data.PopupTxt_Color, 192, F("It looks like the last"));
+ DWINUI::Draw_CenteredString(HMI_data.PopupTxt_Color, 212, F("file was interrupted."));
+ DWINUI::Draw_Icon(ICON_Cancel_E, 26, 307);
+ DWINUI::Draw_Icon(ICON_Continue_E, 146, 307);
+ }
+}
+
+void Draw_Select_Highlight(const bool sel) {
+ HMI_flag.select_flag = sel;
+ const uint16_t c1 = sel ? HMI_data.Highlight_Color : HMI_data.PopupBg_color,
+ c2 = sel ? HMI_data.PopupBg_color : HMI_data.Highlight_Color;
+ DWIN_Draw_Rectangle(0, c1, 25, 279, 126, 318);
+ DWIN_Draw_Rectangle(0, c1, 24, 278, 127, 319);
+ DWIN_Draw_Rectangle(0, c2, 145, 279, 246, 318);
+ DWIN_Draw_Rectangle(0, c2, 144, 278, 247, 319);
+}
+
+void Popup_window_PauseOrStop() {
+ if (HMI_IsChinese()) {
+ DWINUI::ClearMenuArea();
+ Draw_Popup_Bkgd_60();
+ if (select_print.now == 1) DWIN_Frame_AreaCopy(1, 237, 338, 269, 356, 98, 150);
+ else if (select_print.now == 2) DWIN_Frame_AreaCopy(1, 221, 320, 253, 336, 98, 150);
+ DWIN_Frame_AreaCopy(1, 220, 304, 264, 319, 130, 150);
+ DWINUI::Draw_Icon(ICON_Confirm_C, 26, 280);
+ DWINUI::Draw_Icon(ICON_Cancel_C, 146, 280);
+ }
+ else {
+ DWIN_Draw_Popup(ICON_BLTouch, "Please confirm",(select_print.now == 1) ? GET_TEXT(MSG_PAUSE_PRINT) : GET_TEXT(MSG_STOP_PRINT));
+ DWINUI::Draw_Icon(ICON_Confirm_E, 26, 280);
+ DWINUI::Draw_Icon(ICON_Cancel_E, 146, 280);
+ }
+ Draw_Select_Highlight(true);
+}
+
+#if HAS_HOTEND || HAS_HEATED_BED
+ void DWIN_Popup_Temperature(const bool toohigh) {
+ Clear_Popup_Area();
+ Draw_Popup_Bkgd_105();
+ if (toohigh) {
+ DWINUI::Draw_Icon(ICON_TempTooHigh, 102, 165);
+ if (HMI_IsChinese()) {
+ DWIN_Frame_AreaCopy(1, 103, 371, 237, 386, 52, 285);
+ DWIN_Frame_AreaCopy(1, 151, 389, 185, 402, 187, 285);
+ DWIN_Frame_AreaCopy(1, 189, 389, 271, 402, 95, 310);
+ }
+ else {
+ DWINUI::Draw_String(HMI_data.PopupTxt_Color, 36, 300, F("Nozzle or Bed temperature"));
+ DWINUI::Draw_String(HMI_data.PopupTxt_Color, 92, 300, F("is too high"));
+ }
+ }
+ else {
+ DWINUI::Draw_Icon(ICON_TempTooLow, 102, 165);
+ if (HMI_IsChinese()) {
+ DWIN_Frame_AreaCopy(1, 103, 371, 270, 386, 52, 285);
+ DWIN_Frame_AreaCopy(1, 189, 389, 271, 402, 95, 310);
+ }
+ else {
+ DWINUI::Draw_String(HMI_data.PopupTxt_Color, 36, 300, F("Nozzle or Bed temperature"));
+ DWINUI::Draw_String(HMI_data.PopupTxt_Color, 92, 300, F("is too low"));
+ }
+ }
+ }
+
+#endif
+
+void Draw_Print_Labels() {
+ if (HMI_IsChinese()) {
+ Title.FrameCopy(30, 1, 42, 14); // "Printing"
+ DWIN_Frame_AreaCopy(1, 0, 72, 63, 86, 41, 173); // Printing Time
+ DWIN_Frame_AreaCopy(1, 65, 72, 128, 86, 176, 173); // Remain
+ }
+ else {
+ #ifdef USE_STRING_TITLES
+ Title.ShowCaption(GET_TEXT(MSG_PRINTING));
+ DWINUI::Draw_String( 46, 173, F("Print Time"));
+ DWINUI::Draw_String(181, 173, F("Remain"));
+ #else
+ const uint16_t y = 168;
+ Title.FrameCopy(42, 0, 47, 14); // "Printing"
+ DWIN_Frame_AreaCopy(1, 0, 44, 96, 58, 41, y); // Printing Time
+ DWIN_Frame_AreaCopy(1, 98, 44, 152, 58, 176, y); // Remain
+ #endif
+ }
+}
+
+void Draw_Print_ProgressBar() {
+ DWINUI::Draw_Icon(ICON_Bar, 15, 93);
+ DWIN_Draw_Rectangle(1, HMI_data.Barfill_Color, 16 + _percent_done * 240 / 100, 93, 256, 113);
+ DWINUI::Draw_Int(HMI_data.PercentTxt_Color, HMI_data.Background_Color, 3, 117, 133, _percent_done);
+ DWINUI::Draw_String(HMI_data.PercentTxt_Color, 142, 133, F("%"));
+}
+
+void Draw_Print_ProgressElapsed() {
+ char buf[10];
+ duration_t elapsed = print_job_timer.duration(); // print timer
+ sprintf_P(buf, PSTR("%02i:%02i"), (uint16_t)(elapsed.value / 3600), ((uint16_t)elapsed.value % 3600) / 60);
+ DWINUI::Draw_String(HMI_data.Text_Color, HMI_data.Background_Color, 47, 192, buf);
+}
+
+void Draw_Print_ProgressRemain() {
+ char buf[10];
+ sprintf_P(buf, PSTR("%02i:%02i"), (uint16_t)(_remain_time / 3600), ((uint16_t)_remain_time % 3600) / 60);
+ DWINUI::Draw_String(HMI_data.Text_Color, HMI_data.Background_Color, 181, 192, buf);
+}
+
+void ICON_ResumeOrPause() {
+ if (printingIsPaused() || HMI_flag.pause_flag || HMI_flag.pause_action)
+ ICON_Resume();
+ else
+ ICON_Pause();
+}
+
+void Draw_PrintProcess() {
+ DWINUI::ClearMenuArea();
+ Draw_Print_Labels();
+
+ ICON_Tune();
+ ICON_ResumeOrPause();
+ ICON_Stop();
+
+ DWIN_Print_Header(sdprint ? card.longest_filename() : nullptr);
+
+ DWINUI::Draw_Icon(ICON_PrintTime, 15, 173);
+ DWINUI::Draw_Icon(ICON_RemainTime, 150, 171);
+
+ Draw_Print_ProgressBar();
+ Draw_Print_ProgressElapsed();
+ Draw_Print_ProgressRemain();
+
+ DWIN_UpdateLCD();
+}
+
+void Goto_PrintProcess() {
+ checkkey = PrintProcess;
+ Draw_PrintProcess();
+}
+
+void Draw_PrintDone() {
+ // show percent bar and value
+ _percent_done = 100;
+ _remain_time = 0;
+
+ DWINUI::ClearMenuArea();
+ DWIN_Print_Header(nullptr);
+ Draw_Print_Labels();
+ DWINUI::Draw_Icon(ICON_PrintTime, 15, 173);
+ DWINUI::Draw_Icon(ICON_RemainTime, 150, 171);
+ Draw_Print_ProgressBar();
+ Draw_Print_ProgressElapsed();
+ Draw_Print_ProgressRemain();
+
+ // show print done confirm
+ DWIN_Draw_Rectangle(1, HMI_data.Background_Color, 0, 240, DWIN_WIDTH - 1, STATUS_Y - 1);
+ DWINUI::Draw_Icon(HMI_IsChinese() ? ICON_Confirm_C : ICON_Confirm_E, 86, 283);
+}
+
+void Draw_Main_Menu() {
+ DWINUI::ClearMenuArea();
+
+ if (HMI_IsChinese())
+ Title.FrameCopy(2, 2, 26, 13); // "Home" etc
+ else {
+ #ifdef USE_STRING_HEADINGS
+ Title.ShowCaption(MACHINE_NAME);
+ #else
+ Title.FrameCopy(0, 2, 40, 11); // "Home"
+ #endif
+ }
+
+ DWINUI::Draw_Icon(ICON_LOGO, 71, 52); // CREALITY logo
+
+ ICON_Print();
+ ICON_Prepare();
+ ICON_Control();
+ TERN(HAS_ONESTEP_LEVELING, ICON_Leveling, ICON_StartInfo)();
+ DWIN_UpdateLCD();
+}
+
+void Goto_Main_Menu() {
+ checkkey = MainMenu;
+ DWIN_StatusChanged(nullptr);
+ Draw_Main_Menu();
+}
+
+// Draw X, Y, Z and blink if in an un-homed or un-trusted state
+void _update_axis_value(const AxisEnum axis, const uint16_t x, const uint16_t y, const bool blink, const bool force) {
+ const bool draw_qmark = axis_should_home(axis),
+ draw_empty = NONE(HOME_AFTER_DEACTIVATE, DISABLE_REDUCED_ACCURACY_WARNING) && !draw_qmark && !axis_is_trusted(axis);
+
+ // Check for a position change
+ static xyz_pos_t oldpos = { -1, -1, -1 };
+ const float p = current_position[axis];
+ const bool changed = oldpos[axis] != p;
+ if (changed) oldpos[axis] = p;
+
+ if (force || changed || draw_qmark || draw_empty) {
+ if (blink && draw_qmark)
+ DWINUI::Draw_String(HMI_data.Coordinate_Color, HMI_data.Background_Color, x, y, F("--?--"));
+ else if (blink && draw_empty)
+ DWINUI::Draw_String(HMI_data.Coordinate_Color, HMI_data.Background_Color, x, y, F(" "));
+ else
+ DWINUI::Draw_Signed_Float(HMI_data.Coordinate_Color, HMI_data.Background_Color, 3, 1, x, y, p);
+ }
+}
+
+void _draw_xyz_position(const bool force) {
+ //SERIAL_ECHOPGM("Draw XYZ:");
+ static bool _blink = false;
+ const bool blink = !!(millis() & 0x400UL);
+ if (force || blink != _blink) {
+ _blink = blink;
+ //SERIAL_ECHOPGM(" (blink)");
+ _update_axis_value(X_AXIS, 35, 459, blink, true);
+ _update_axis_value(Y_AXIS, 120, 459, blink, true);
+ _update_axis_value(Z_AXIS, 205, 459, blink, true);
+ }
+ //SERIAL_EOL();
+}
+
+void update_variable() {
+ #if HAS_HOTEND
+ static celsius_t _hotendtemp = 0, _hotendtarget = 0;
+ const celsius_t hc = thermalManager.wholeDegHotend(0),
+ ht = thermalManager.degTargetHotend(0);
+ const bool _new_hotend_temp = _hotendtemp != hc,
+ _new_hotend_target = _hotendtarget != ht;
+ if (_new_hotend_temp) _hotendtemp = hc;
+ if (_new_hotend_target) _hotendtarget = ht;
+ #endif
+ #if HAS_HEATED_BED
+ static celsius_t _bedtemp = 0, _bedtarget = 0;
+ const celsius_t bc = thermalManager.wholeDegBed(),
+ bt = thermalManager.degTargetBed();
+ const bool _new_bed_temp = _bedtemp != bc,
+ _new_bed_target = _bedtarget != bt;
+ if (_new_bed_temp) _bedtemp = bc;
+ if (_new_bed_target) _bedtarget = bt;
+ #endif
+ #if HAS_FAN
+ static uint8_t _fanspeed = 0;
+ const bool _new_fanspeed = _fanspeed != thermalManager.fan_speed[0];
+ if (_new_fanspeed) _fanspeed = thermalManager.fan_speed[0];
+ #endif
+
+ if (checkkey == Menu && (CurrentMenu == TuneMenu || CurrentMenu == TemperatureMenu)) {
+ // Tune page temperature update
+ #if HAS_HOTEND
+ if (_new_hotend_target)
+ HotendTargetItem->Draw(CurrentMenu->line(HotendTargetItem->pos));
+ #endif
+ #if HAS_HEATED_BED
+ if (_new_bed_target)
+ BedTargetItem->Draw(CurrentMenu->line(BedTargetItem->pos));
+ #endif
+ #if HAS_FAN
+ if (_new_fanspeed)
+ FanSpeedItem->Draw(CurrentMenu->line(FanSpeedItem->pos));
+ #endif
+ }
+
+ // Bottom temperature update
+
+ #if HAS_HOTEND
+ if (_new_hotend_temp)
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 28, 384, _hotendtemp);
+ if (_new_hotend_target)
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 25 + 4 * STAT_CHR_W + 6, 384, _hotendtarget);
+
+ static int16_t _flow = planner.flow_percentage[0];
+ if (_flow != planner.flow_percentage[0]) {
+ _flow = planner.flow_percentage[0];
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 116 + 2 * STAT_CHR_W, 417, _flow);
+ }
+ #endif
+
+ #if HAS_HEATED_BED
+ if (_new_bed_temp)
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 28, 417, _bedtemp);
+ if (_new_bed_target)
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 25 + 4 * STAT_CHR_W + 6, 417, _bedtarget);
+ #endif
+
+ static int16_t _feedrate = 100;
+ if (_feedrate != feedrate_percentage) {
+ _feedrate = feedrate_percentage;
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 116 + 2 * STAT_CHR_W, 384, _feedrate);
+ }
+
+ #if HAS_FAN
+ if (_new_fanspeed) {
+ _fanspeed = thermalManager.fan_speed[0];
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 195 + 2 * STAT_CHR_W, 384, _fanspeed);
+ }
+ #endif
+
+ static float _offset = 0;
+ if (BABY_Z_VAR != _offset) {
+ _offset = BABY_Z_VAR;
+ DWINUI::Draw_Signed_Float(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 2, 2, 210, 417, _offset);
+ }
+
+ _draw_xyz_position(false);
+}
+
+/**
+ * Read and cache the working directory.
+ *
+ * TODO: New code can follow the pattern of menu_media.cpp
+ * and rely on Marlin caching for performance. No need to
+ * cache files here.
+ */
+
+#ifndef strcasecmp_P
+ #define strcasecmp_P(a, b) strcasecmp((a), (b))
+#endif
+
+void make_name_without_ext(char *dst, char *src, size_t maxlen=MENU_CHAR_LIMIT) {
+ char * const name = card.longest_filename();
+ size_t pos = strlen(name); // index of ending nul
+
+ // For files, remove the extension
+ // which may be .gcode, .gco, or .g
+ if (!card.flag.filenameIsDir)
+ while (pos && src[pos] != '.') pos--; // find last '.' (stop at 0)
+
+ size_t len = pos; // nul or '.'
+ if (len > maxlen) { // Keep the name short
+ pos = len = maxlen; // move nul down
+ dst[--pos] = '.'; // insert dots
+ dst[--pos] = '.';
+ dst[--pos] = '.';
+ }
+
+ dst[len] = '\0'; // end it
+
+ // Copy down to 0
+ while (pos--) dst[pos] = src[pos];
+}
+
+void HMI_SDCardInit() { card.cdroot(); }
+
+void MarlinUI::refresh() { /* Nothing to see here */ }
+
+#define ICON_Folder ICON_More
+
+#if ENABLED(SCROLL_LONG_FILENAMES)
+
+ char shift_name[LONG_FILENAME_LENGTH + 1];
+ int8_t shift_amt; // = 0
+ millis_t shift_ms; // = 0
+
+ // Init the shift name based on the highlighted item
+ void Init_Shift_Name() {
+ const bool is_subdir = !card.flag.workDirIsRoot;
+ const int8_t filenum = select_file.now - 1 - is_subdir; // Skip "Back" and ".."
+ const uint16_t fileCnt = card.get_num_Files();
+ if (WITHIN(filenum, 0, fileCnt - 1)) {
+ card.getfilename_sorted(SD_ORDER(filenum, fileCnt));
+ char * const name = card.longest_filename();
+ make_name_without_ext(shift_name, name, 100);
+ }
+ }
+
+ void Init_SDItem_Shift() {
+ shift_amt = 0;
+ shift_ms = select_file.now > 0 && strlen(shift_name) > MENU_CHAR_LIMIT ? millis() + 750UL : 0;
+ }
+
+#endif
+
+/**
+ * Display an SD item, adding a CDUP for subfolders.
+ */
+void Draw_SDItem(const uint16_t item, int16_t row=-1) {
+ if (row < 0) row = item + 1 + MROWS - index_file;
+ const bool is_subdir = !card.flag.workDirIsRoot;
+ if (is_subdir && item == 0)
+ return Draw_Menu_Line(row, ICON_Folder, "..");
+
+ card.getfilename_sorted(SD_ORDER(item - is_subdir, card.get_num_Files()));
+ char * const name = card.longest_filename();
+
+ #if ENABLED(SCROLL_LONG_FILENAMES)
+ // Init the current selected name
+ // This is used during scroll drawing
+ if (item == select_file.now - 1) {
+ make_name_without_ext(shift_name, name, 100);
+ Init_SDItem_Shift();
+ }
+ #endif
+
+ // Draw the file/folder with name aligned left
+ char str[strlen(name) + 1];
+ make_name_without_ext(str, name);
+ Draw_Menu_Line(row, card.flag.filenameIsDir ? ICON_Folder : ICON_File, str);
+}
+
+#if ENABLED(SCROLL_LONG_FILENAMES)
+
+ void Draw_SDItem_Shifted(uint8_t &shift) {
+ // Limit to the number of chars past the cutoff
+ const size_t len = strlen(shift_name);
+ NOMORE(shift, _MAX(len - MENU_CHAR_LIMIT, 0U));
+
+ // Shorten to the available space
+ const size_t lastchar = _MIN((signed)len, shift + MENU_CHAR_LIMIT);
+
+ const char c = shift_name[lastchar];
+ shift_name[lastchar] = '\0';
+
+ const uint8_t row = select_file.now + MROWS - index_file; // skip "Back" and scroll
+ Erase_Menu_Text(row);
+ Draw_Menu_Line(row, 0, &shift_name[shift]);
+
+ shift_name[lastchar] = c;
+ }
+
+#endif
+
+// Redraw the first set of SD Files
+void Redraw_SD_List() {
+ select_file.reset();
+ index_file = MROWS;
+
+ DWINUI::ClearMenuArea(); // Leave title bar unchanged
+
+ Draw_Back_First();
+
+ if (card.isMounted()) {
+ // As many files as will fit
+ LOOP_L_N(i, _MIN(nr_sd_menu_items(), MROWS))
+ Draw_SDItem(i, i + 1);
+
+ TERN_(SCROLL_LONG_FILENAMES, Init_SDItem_Shift());
+ }
+ else {
+ DWIN_Draw_Rectangle(1, HMI_data.AlertBg_Color, 10, MBASE(3) - 10, DWIN_WIDTH - 10, MBASE(4));
+ DWINUI::Draw_CenteredString(font16x32, HMI_data.AlertTxt_Color, MBASE(3), F("No Media"));
+ }
+}
+
+bool DWIN_lcd_sd_status = false;
+
+void SDCard_Up() {
+ card.cdup();
+ Redraw_SD_List();
+ DWIN_lcd_sd_status = false; // On next DWIN_Update
+}
+
+void SDCard_Folder(char * const dirname) {
+ card.cd(dirname);
+ Redraw_SD_List();
+ DWIN_lcd_sd_status = false; // On next DWIN_Update
+}
+
+//
+// Watch for media mount / unmount
+//
+void HMI_SDCardUpdate() {
+ if (HMI_flag.home_flag) return;
+ if (DWIN_lcd_sd_status != card.isMounted()) {
+ DWIN_lcd_sd_status = card.isMounted();
+ //SERIAL_ECHOLNPAIR("HMI_SDCardUpdate: ", DWIN_lcd_sd_status);
+ if (DWIN_lcd_sd_status) {
+ if (checkkey == SelectFile)
+ Redraw_SD_List();
+ }
+ else {
+ // clean file icon
+ if (checkkey == SelectFile) {
+ Redraw_SD_List();
+ }
+ else if (sdprint && card.isPrinting() && printingIsActive()) {
+ // TODO: Move card removed abort handling
+ // to CardReader::manage_media.
+ card.abortFilePrintSoon();
+ wait_for_heatup = wait_for_user = false;
+ dwin_abort_flag = true; // Reset feedrate, return to Home
+ }
+ }
+ DWIN_UpdateLCD();
+ }
+}
+
+//
+// The status area is always on-screen, except during
+// full-screen modal dialogs. (TODO: Keep alive during dialogs)
+//
+void Draw_Status_Area(const bool with_update) {
+
+ DWIN_Draw_Rectangle(1, HMI_data.Background_Color, 0, STATUS_Y + 21, DWIN_WIDTH, DWIN_HEIGHT - 1);
+
+ #if HAS_HOTEND
+ DWINUI::Draw_Icon(ICON_HotendTemp, 10, 383);
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 28, 384, thermalManager.wholeDegHotend(0));
+ DWINUI::Draw_String(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 25 + 3 * STAT_CHR_W + 5, 384, F("/"));
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 25 + 4 * STAT_CHR_W + 6, 384, thermalManager.degTargetHotend(0));
+
+ DWINUI::Draw_Icon(ICON_StepE, 112, 417);
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 116 + 2 * STAT_CHR_W, 417, planner.flow_percentage[0]);
+ DWINUI::Draw_String(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 116 + 5 * STAT_CHR_W + 2, 417, F("%"));
+ #endif
+
+ #if HAS_HEATED_BED
+ DWINUI::Draw_Icon(ICON_BedTemp, 10, 416);
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 28, 417, thermalManager.wholeDegBed());
+ DWINUI::Draw_String(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 25 + 3 * STAT_CHR_W + 5, 417, F("/"));
+ DWINUI::Draw_Int(true, true, 0, DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 25 + 4 * STAT_CHR_W + 6, 417, thermalManager.degTargetBed());
+ #endif
+
+ DWINUI::Draw_Icon(ICON_Speed, 113, 383);
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 116 + 2 * STAT_CHR_W, 384, feedrate_percentage);
+ DWINUI::Draw_String(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 116 + 5 * STAT_CHR_W + 2, 384, F("%"));
+
+ #if HAS_FAN
+ DWINUI::Draw_Icon(ICON_FanSpeed, 187, 383);
+ DWINUI::Draw_Int(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 3, 195 + 2 * STAT_CHR_W, 384, thermalManager.fan_speed[0]);
+ #endif
+
+ #if HAS_ZOFFSET_ITEM
+ DWINUI::Draw_Icon(ICON_Zoffset, 187, 416);
+ #endif
+
+ DWINUI::Draw_Signed_Float(DWIN_FONT_STAT, HMI_data.Indicator_Color, HMI_data.Background_Color, 2, 2, 210, 417, BABY_Z_VAR);
+
+ DWIN_Draw_Rectangle(1, HMI_data.SplitLine_Color, 0, 449, DWIN_WIDTH, 451);
+
+ DWINUI::Draw_Icon(ICON_MaxSpeedX, 10, 456);
+ DWINUI::Draw_Icon(ICON_MaxSpeedY, 95, 456);
+ DWINUI::Draw_Icon(ICON_MaxSpeedZ, 180, 456);
+ _draw_xyz_position(true);
+
+ if (with_update) {
+ DWIN_UpdateLCD();
+ delay(5);
+ }
+}
+
+void HMI_StartFrame(const bool with_update) {
+ Goto_Main_Menu();
+ Draw_Status_Area(with_update);
+}
+
+void Draw_Info_Menu() {
+ DWINUI::ClearMenuArea();
+ Draw_Back_First();
+
+ DWINUI::Draw_CenteredString(122, F(MACHINE_SIZE));
+ DWINUI::Draw_CenteredString(195, F(SHORT_BUILD_VERSION));
+
+ if (HMI_IsChinese()) {
+ Title.FrameCopy(30, 17, 28, 13); // "Info"
+
+ DWIN_Frame_AreaCopy(1, 197, 149, 252, 161, 108, 102); // "Size"
+ DWIN_Frame_AreaCopy(1, 1, 164, 56, 176, 108, 175); // "Firmware Version"
+ DWIN_Frame_AreaCopy(1, 58, 164, 113, 176, 105, 248); // "Contact Details"
+ }
+ else {
+ #ifdef USE_STRING_HEADINGS
+ Title.ShowCaption(GET_TEXT_F(MSG_INFO_SCREEN));
+ #else
+ Title.FrameCopy(192, 15, 23, 12); // "Info"
+ #endif
+
+ DWIN_Frame_AreaCopy(1, 120, 150, 146, 161, 124, 102); // "Size"
+ DWIN_Frame_AreaCopy(1, 146, 151, 254, 161, 82, 175); // "Firmware Version"
+ DWIN_Frame_AreaCopy(1, 1, 164, 96, 175, 89, 248); // "Contact details"
+ }
+ DWINUI::Draw_CenteredString(268, F(CORP_WEBSITE));
+
+ LOOP_L_N(i, 3) {
+ DWINUI::Draw_Icon(ICON_PrintSize + i, 26, 99 + i * 73);
+ DWIN_Draw_Line(HMI_data.SplitLine_Color, 16, MBASE(2) + i * 73, 256, 156 + i * 73);
+ }
+
+ DWIN_UpdateLCD();
+}
+
+void Draw_Print_File_Menu() {
+ if (HMI_IsChinese())
+ Title.FrameCopy(0, 31, 56, 14); // "Print file"
+ else {
+ #ifdef USE_STRING_HEADINGS
+ Title.ShowCaption(GET_TEXT_F(MSG_MEDIA_MENU));
+ #else
+ Title.FrameCopy(52, 31, 86, 11); // "Print file"
+ #endif
+ }
+ Redraw_SD_List();
+}
+
+// Main Process
+void HMI_MainMenu() {
+ ENCODER_DiffState encoder_diffState = get_encoder_state();
+ if (encoder_diffState == ENCODER_DIFF_NO) return;
+
+ if (encoder_diffState == ENCODER_DIFF_CW) {
+ if (select_page.inc(4)) {
+ switch (select_page.now) {
+ case 0: ICON_Print(); break;
+ case 1: ICON_Print(); ICON_Prepare(); break;
+ case 2: ICON_Prepare(); ICON_Control(); break;
+ case 3: ICON_Control(); TERN(HAS_ONESTEP_LEVELING, ICON_Leveling, ICON_StartInfo)(); break;
+ }
+ }
+ }
+ else if (encoder_diffState == ENCODER_DIFF_CCW) {
+ if (select_page.dec()) {
+ switch (select_page.now) {
+ case 0: ICON_Print(); ICON_Prepare(); break;
+ case 1: ICON_Prepare(); ICON_Control(); break;
+ case 2: ICON_Control(); TERN(HAS_ONESTEP_LEVELING, ICON_Leveling, ICON_StartInfo)(); break;
+ case 3: TERN(HAS_ONESTEP_LEVELING, ICON_Leveling, ICON_StartInfo)(); break;
+ }
+ }
+ }
+ else if (encoder_diffState == ENCODER_DIFF_ENTER) {
+ switch (select_page.now) {
+ case 0: // Print File
+ checkkey = SelectFile;
+ Draw_Print_File_Menu();
+ break;
+
+ case 1: // Prepare
+ Draw_Prepare_Menu();
+ break;
+
+ case 2: // Control
+ Draw_Control_Menu();
+ break;
+
+ case 3: // Leveling or Info
+ #if HAS_ONESTEP_LEVELING
+ queue.inject_P(PSTR("G28XYO\nG28Z\nG29"));
+ #else
+ checkkey = Info;
+ Draw_Info_Menu();
+ #endif
+ break;
+ }
+ }
+ DWIN_UpdateLCD();
+}
+
+// Select (and Print) File
+void HMI_SelectFile() {
+ ENCODER_DiffState encoder_diffState = get_encoder_state();
+
+ const uint16_t hasUpDir = !card.flag.workDirIsRoot;
+
+ if (encoder_diffState == ENCODER_DIFF_NO) {
+ #if ENABLED(SCROLL_LONG_FILENAMES)
+ if (shift_ms && select_file.now >= 1 + hasUpDir) {
+ // Scroll selected filename every second
+ const millis_t ms = millis();
+ if (ELAPSED(ms, shift_ms)) {
+ const bool was_reset = shift_amt < 0;
+ shift_ms = ms + 375UL + was_reset * 250UL; // ms per character
+ uint8_t shift_new = shift_amt + 1; // Try to shift by...
+ Draw_SDItem_Shifted(shift_new); // Draw the item
+ if (!was_reset && shift_new == 0) // Was it limited to 0?
+ shift_ms = 0; // No scrolling needed
+ else if (shift_new == shift_amt) // Scroll reached the end
+ shift_new = -1; // Reset
+ shift_amt = shift_new; // Set new scroll
+ }
+ }
+ #endif
+ return;
+ }
+
+ // First pause is long. Easy.
+ // On reset, long pause must be after 0.
+
+ const uint16_t fullCnt = nr_sd_menu_items();
+
+ if (encoder_diffState == ENCODER_DIFF_CW && fullCnt) {
+ if (select_file.inc(1 + fullCnt)) {
+ const uint8_t itemnum = select_file.now - 1; // -1 for "Back"
+ if (TERN0(SCROLL_LONG_FILENAMES, shift_ms)) { // If line was shifted
+ Erase_Menu_Text(itemnum + MROWS - index_file); // Erase and
+ Draw_SDItem(itemnum - 1); // redraw
+ }
+ if (select_file.now > MROWS && select_file.now > index_file) { // Cursor past the bottom
+ index_file = select_file.now; // New bottom line
+ Scroll_Menu(DWIN_SCROLL_UP);
+ Draw_SDItem(itemnum, MROWS); // Draw and init the shift name
+ }
+ else {
+ Move_Highlight(1, select_file.now + MROWS - index_file); // Just move highlight
+ TERN_(SCROLL_LONG_FILENAMES, Init_Shift_Name()); // ...and init the shift name
+ }
+ TERN_(SCROLL_LONG_FILENAMES, Init_SDItem_Shift());
+ }
+ }
+ else if (encoder_diffState == ENCODER_DIFF_CCW && fullCnt) {
+ if (select_file.dec()) {
+ const uint8_t itemnum = select_file.now - 1; // -1 for "Back"
+ if (TERN0(SCROLL_LONG_FILENAMES, shift_ms)) { // If line was shifted
+ Erase_Menu_Text(select_file.now + 1 + MROWS - index_file); // Erase and
+ Draw_SDItem(itemnum + 1); // redraw
+ }
+ if (select_file.now < index_file - MROWS) { // Cursor past the top
+ index_file--; // New bottom line
+ Scroll_Menu(DWIN_SCROLL_DOWN);
+ if (index_file == MROWS) {
+ Draw_Back_First();
+ TERN_(SCROLL_LONG_FILENAMES, shift_ms = 0);
+ }
+ else {
+ Draw_SDItem(itemnum, 0); // Draw the item (and init shift name)
+ }
+ }
+ else {
+ Move_Highlight(-1, select_file.now + MROWS - index_file); // Just move highlight
+ TERN_(SCROLL_LONG_FILENAMES, Init_Shift_Name()); // ...and init the shift name
+ }
+ TERN_(SCROLL_LONG_FILENAMES, Init_SDItem_Shift()); // Reset left. Init timer.
+ }
+ }
+ else if (encoder_diffState == ENCODER_DIFF_ENTER) {
+ if (select_file.now == 0) { // Back
+ select_page.set(0);
+ Goto_Main_Menu();
+ }
+ else if (hasUpDir && select_file.now == 1) { // CD-Up
+ SDCard_Up();
+ goto HMI_SelectFileExit;
+ }
+ else {
+ const uint16_t filenum = select_file.now - 1 - hasUpDir;
+ card.getfilename_sorted(SD_ORDER(filenum, card.get_num_Files()));
+
+ // Enter that folder!
+ if (card.flag.filenameIsDir) {
+ SDCard_Folder(card.filename);
+ goto HMI_SelectFileExit;
+ }
+
+ // Reset highlight for next entry
+ select_print.reset();
+ select_file.reset();
+
+ // Start choice and print SD file
+ HMI_flag.heat_flag = true;
+ HMI_flag.print_finish = false;
+
+ card.openAndPrintFile(card.filename);
+
+ #if HAS_FAN
+ // All fans on for Ender 3 v2 ?
+ // The slicer should manage this for us.
+ //for (uint8_t i = 0; i < FAN_COUNT; i++)
+ // thermalManager.fan_speed[i] = 255;
+ #endif
+
+ DWIN_Print_Started(true);
+ }
+ }
+HMI_SelectFileExit:
+ DWIN_UpdateLCD();
+}
+
+// Printing
+void HMI_Printing() {
+ ENCODER_DiffState encoder_diffState = get_encoder_state();
+ if (encoder_diffState == ENCODER_DIFF_NO) return;
+ // Avoid flicker by updating only the previous menu
+ if (encoder_diffState == ENCODER_DIFF_CW) {
+ if (select_print.inc(3)) {
+ switch (select_print.now) {
+ case 0: ICON_Tune(); break;
+ case 1:
+ ICON_Tune();
+ ICON_ResumeOrPause();
+ break;
+ case 2:
+ ICON_ResumeOrPause();
+ ICON_Stop();
+ break;
+ }
+ }
+ }
+ else if (encoder_diffState == ENCODER_DIFF_CCW) {
+ if (select_print.dec()) {
+ switch (select_print.now) {
+ case 0:
+ ICON_Tune();
+ ICON_ResumeOrPause();
+ break;
+ case 1:
+ ICON_ResumeOrPause();
+ ICON_Stop();
+ break;
+ case 2: ICON_Stop(); break;
+ }
+ }
+ }
+ else if (encoder_diffState == ENCODER_DIFF_ENTER) {
+ switch (select_print.now) {
+ case 0: // Tune
+ Draw_Tune_Menu();
+ break;
+ case 1: // Pause
+ if (HMI_flag.pause_flag) {
+ ICON_Pause();
+ #ifndef ADVANCED_PAUSE_FEATURE
+ char cmd[40];
+ cmd[0] = '\0';
+ #if BOTH(HAS_HEATED_BED, PAUSE_HEAT)
+ if (resume_bed_temp) sprintf_P(cmd, PSTR("M190 S%i\n"), resume_bed_temp);
+ #endif
+ #if BOTH(HAS_HOTEND, PAUSE_HEAT)
+ if (resume_hotend_temp) sprintf_P(&cmd[strlen(cmd)], PSTR("M109 S%i\n"), resume_hotend_temp);
+ #endif
+ #if HAS_FAN
+ if (resume_fan) thermalManager.fan_speed[0] = resume_fan;
+ #endif
+ strcat_P(cmd, M24_STR);
+ queue.inject(cmd);
+ #endif
+ }
+ else {
+ HMI_flag.select_flag = true;
+ checkkey = PauseOrStop;
+ Popup_window_PauseOrStop();
+ }
+ break;
+
+ case 2: // Stop
+ HMI_flag.select_flag = true;
+ checkkey = PauseOrStop;
+ Popup_window_PauseOrStop();
+ break;
+
+ default: break;
+ }
+ }
+ DWIN_UpdateLCD();
+}
+
+// Print done
+void HMI_PrintDone() {
+ ENCODER_DiffState encoder_diffState = get_encoder_state();
+ if (encoder_diffState == ENCODER_DIFF_NO) return;
+ if (encoder_diffState == ENCODER_DIFF_ENTER) {
+ dwin_abort_flag = true; // Reset feedrate, return to Home
+ Goto_Main_Menu(); // Return to Main menu after print done
+ }
+}
+
+// Pause or Stop popup
+void HMI_PauseOrStop() {
+ ENCODER_DiffState encoder_diffState = get_encoder_state();
+ if (encoder_diffState == ENCODER_DIFF_NO) return;
+
+ if (encoder_diffState == ENCODER_DIFF_CW)
+ Draw_Select_Highlight(false);
+ else if (encoder_diffState == ENCODER_DIFF_CCW)
+ Draw_Select_Highlight(true);
+ else if (encoder_diffState == ENCODER_DIFF_ENTER) {
+ if (select_print.now == 1) { // pause window
+ if (HMI_flag.select_flag) {
+ HMI_flag.pause_action = true;
+ ICON_Resume();
+ queue.inject_P(PSTR("M25"));
+ }
+ else {
+ // cancel pause
+ }
+ Goto_PrintProcess();
+ }
+ else if (select_print.now == 2) { // stop window
+ if (HMI_flag.select_flag) {
+ checkkey = MainMenu;
+ if (HMI_flag.home_flag) planner.synchronize(); // Wait for planner moves to finish!
+ wait_for_heatup = wait_for_user = false; // Stop waiting for heating/user
+ card.abortFilePrintSoon(); // Let the main loop handle SD abort
+ dwin_abort_flag = true; // Reset feedrate, return to Home
+ #ifdef ACTION_ON_CANCEL
+ host_action_cancel();
+ #endif
+ DWIN_Draw_Popup(ICON_BLTouch, "Stopping..." , "Please wait until done.");
+ }
+ else
+ Goto_PrintProcess(); // cancel stop
+ }
+ }
+ DWIN_UpdateLCD();
+}
+
+#include "../../../libs/buzzer.h"
+
+void HMI_AudioFeedback(const bool success/*=true*/) {
+ #if HAS_BUZZER
+ if (success) {
+ BUZZ(100, 659);
+ BUZZ(10, 0);
+ BUZZ(100, 698);
+ }
+ else
+ BUZZ(40, 440);
+ #endif
+}
+
+void Draw_Main_Area() {
+ switch (checkkey) {
+ case MainMenu: Draw_Main_Menu(); break;
+ case SelectFile: Draw_Print_File_Menu(); break;
+ case PrintProcess: Draw_PrintProcess(); break;
+ case PrintDone: Draw_PrintDone(); break;
+ case Info: Draw_Info_Menu(); break;
+ case PauseOrStop: Popup_window_PauseOrStop(); break;
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ case FilamentPurge: Draw_Popup_FilamentPurge(); break;
+ #endif
+ case Locked: LockScreen.Draw(); break;
+ case Menu:
+ case SetInt:
+ case SetPInt:
+ case SetIntNoDraw:
+ case SetFloat:
+ case SetPFloat: CurrentMenu->Draw(); break;
+ default: break;
+ }
+}
+
+void HMI_ReturnScreen() {
+ checkkey = last_checkkey;
+ Draw_Main_Area();
+ DWIN_UpdateLCD();
+ return;
+}
+
+void HMI_Popup() {
+ ENCODER_DiffState encoder_diffState = get_encoder_state();
+ if (encoder_diffState == ENCODER_DIFF_NO) return;
+ if (encoder_diffState == ENCODER_DIFF_ENTER) {
+ wait_for_user = false;
+ HMI_ReturnScreen();
+ }
+}
+
+void HMI_Init() {
+ HMI_SDCardInit();
+
+ for (uint16_t t = 0; t <= 100; t += 2) {
+ DWINUI::Draw_Icon(ICON_Bar, 15, 260);
+ DWIN_Draw_Rectangle(1, HMI_data.Background_Color, 15 + t * 242 / 100, 260, 257, 280);
+ DWIN_UpdateLCD();
+ delay(20);
+ }
+
+ HMI_SetLanguage();
+}
+
+void DWIN_Update() {
+ EachMomentUpdate(); // Status update
+ HMI_SDCardUpdate(); // SD card update
+ DWIN_HandleScreen(); // Rotary encoder update
+}
+
+void EachMomentUpdate() {
+ static millis_t next_var_update_ms = 0, next_rts_update_ms = 0;
+
+ const millis_t ms = millis();
+ if (ELAPSED(ms, next_var_update_ms)) {
+ next_var_update_ms = ms + DWIN_VAR_UPDATE_INTERVAL;
+ update_variable();
+ }
+
+ if (PENDING(ms, next_rts_update_ms)) return;
+ next_rts_update_ms = ms + DWIN_SCROLL_UPDATE_INTERVAL;
+
+ if (checkkey == PrintProcess) {
+ // if print done
+ if (HMI_flag.print_finish) {
+ HMI_flag.print_finish = false;
+ TERN_(POWER_LOSS_RECOVERY, recovery.cancel());
+ planner.finish_and_disable();
+ checkkey = PrintDone;
+ Draw_PrintDone();
+ }
+ else if (HMI_flag.pause_flag != printingIsPaused()) {
+ // print status update
+ HMI_flag.pause_flag = printingIsPaused();
+ ICON_ResumeOrPause();
+ }
+ }
+
+ // pause after homing
+ if (HMI_flag.pause_action && printingIsPaused() && !planner.has_blocks_queued()) {
+ HMI_flag.pause_action = false;
+ #if ENABLED(PAUSE_HEAT)
+ if (sdprint) {
+ TERN_(HAS_HOTEND, resume_hotend_temp = thermalManager.degTargetHotend(0));
+ TERN_(HAS_HEATED_BED, resume_bed_temp = thermalManager.degTargetBed());
+ }
+ else {
+ TERN_(HAS_HOTEND, resume_hotend_temp = thermalManager.wholeDegHotend(0));
+ TERN_(HAS_HEATED_BED, resume_bed_temp = thermalManager.wholeDegBed());
+ }
+ TERN_(HAS_FAN, resume_fan = thermalManager.fan_speed[0]);
+ #endif
+ #if DISABLED(ADVANCED_PAUSE_FEATURE)
+ thermalManager.disable_all_heaters();
+ #endif
+ #if DISABLED(PARK_HEAD_ON_PAUSE)
+ queue.inject_P(PSTR("G1 F1200 X0 Y0"));
+ #endif
+ }
+
+ if (checkkey == PrintProcess) { // print process
+
+ duration_t elapsed = print_job_timer.duration(); // print timer
+
+ if (sdprint && card.isPrinting()) {
+ uint8_t percentDone = card.percentDone();
+ static uint8_t last_percentValue = 101;
+ if (last_percentValue != percentDone) { // print percent
+ last_percentValue = percentDone;
+ if (percentDone) {
+ _percent_done = percentDone;
+ Draw_Print_ProgressBar();
+ }
+ }
+
+ // Estimate remaining time every 20 seconds
+ static millis_t next_remain_time_update = 0;
+ if (_percent_done > 1 && ELAPSED(ms, next_remain_time_update) && !HMI_flag.heat_flag) {
+ _remain_time = (elapsed.value - dwin_heat_time) / (_percent_done * 0.01f) - (elapsed.value - dwin_heat_time);
+ next_remain_time_update += DWIN_REMAIN_TIME_UPDATE_INTERVAL;
+ Draw_Print_ProgressRemain();
+ }
+ }
+
+ // Print time so far
+ static uint16_t last_Printtime = 0;
+ const uint16_t min = (elapsed.value % 3600) / 60;
+ if (last_Printtime != min) { // 1 minute update
+ last_Printtime = min;
+ Draw_Print_ProgressElapsed();
+ }
+
+ }
+ else if (dwin_abort_flag && !HMI_flag.home_flag) { // Print Stop
+ dwin_abort_flag = false;
+ dwin_zoffset = BABY_Z_VAR;
+ select_page.set(0);
+ Goto_Main_Menu();
+ }
+
+ #if ENABLED(POWER_LOSS_RECOVERY)
+ else if (DWIN_lcd_sd_status && recovery.dwin_flag) { // resume print before power off
+ static bool recovery_flag = false;
+
+ recovery.dwin_flag = false;
+ recovery_flag = true;
+
+ auto update_selection = [&](const bool sel) {
+ HMI_flag.select_flag = sel;
+ const uint16_t c1 = sel ? HMI_data.PopupBg_color : HMI_data.Highlight_Color;
+ DWIN_Draw_Rectangle(0, c1, 25, 306, 126, 345);
+ DWIN_Draw_Rectangle(0, c1, 24, 305, 127, 346);
+ const uint16_t c2 = sel ? HMI_data.Highlight_Color : HMI_data.PopupBg_color;
+ DWIN_Draw_Rectangle(0, c2, 145, 306, 246, 345);
+ DWIN_Draw_Rectangle(0, c2, 144, 305, 247, 346);
+ };
+
+ Popup_Window_Resume();
+ update_selection(true);
+
+ // TODO: Get the name of the current file from someplace
+ //
+ //(void)recovery.interrupted_file_exists();
+ SdFile *dir = nullptr;
+ const char * const filename = card.diveToFile(true, dir, recovery.info.sd_filename);
+ card.selectFileByName(filename);
+ DWINUI::Draw_CenteredString(HMI_data.PopupTxt_Color, 252, card.longest_filename());
+ DWIN_UpdateLCD();
+
+ while (recovery_flag) {
+ ENCODER_DiffState encoder_diffState = Encoder_ReceiveAnalyze();
+ if (encoder_diffState != ENCODER_DIFF_NO) {
+ if (encoder_diffState == ENCODER_DIFF_ENTER) {
+ recovery_flag = false;
+ if (HMI_flag.select_flag) break;
+ TERN_(POWER_LOSS_RECOVERY, queue.inject_P(PSTR("M1000C")));
+ return HMI_StartFrame(true);
+ }
+ else
+ update_selection(encoder_diffState == ENCODER_DIFF_CW);
+
+ DWIN_UpdateLCD();
+ }
+ watchdog_refresh();
+ }
+
+ select_print.set(0);
+ queue.inject_P(PSTR("M1000"));
+ sdprint = true;
+ Goto_PrintProcess();
+ Draw_Status_Area(true);
+ }
+ #endif // POWER_LOSS_RECOVERY
+
+ DWIN_UpdateLCD();
+}
+
+void DWIN_HandleScreen() {
+ switch (checkkey) {
+ case MainMenu: HMI_MainMenu(); break;
+ case Menu: HMI_Menu(); break;
+ case SetInt: HMI_SetInt(); break;
+ case SetPInt: HMI_SetPInt(); break;
+ case SetIntNoDraw: HMI_SetIntNoDraw(); break;
+ case SetFloat: HMI_SetFloat(); break;
+ case SetPFloat: HMI_SetPFloat(); break;
+ case SelectFile: HMI_SelectFile(); break;
+ case Homing: break;
+ case Leveling: break;
+ case PrintProcess: HMI_Printing(); break;
+ case PrintDone: HMI_PrintDone(); break;
+ case PauseOrStop: HMI_PauseOrStop(); break;
+ case Info: HMI_Popup(); break;
+ case WaitResponse: HMI_Popup(); break;
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ case FilamentPurge: HMI_FilamentPurge(); break;
+ #endif
+ case NothingToDo: break;
+ case Locked: HMI_LockScreen(); break;
+ default: break;
+ }
+}
+
+void HMI_SaveProcessID(const uint8_t id) {
+ if (checkkey != id) {
+ if ((checkkey != NothingToDo) &&
+ (checkkey != WaitResponse) &&
+ (checkkey != Homing) &&
+ (checkkey != Leveling) &&
+ (checkkey != PauseOrStop) &&
+ (checkkey != FilamentPurge)) last_checkkey = checkkey; // if not a popup
+ checkkey = id;
+ }
+}
+
+void DWIN_StartHoming() {
+ HMI_flag.home_flag = true;
+ HMI_SaveProcessID(Homing);
+ DWIN_Draw_Popup(ICON_BLTouch, "Axis Homing", "Please wait until done.");
+}
+
+void DWIN_CompletedHoming() {
+ HMI_flag.home_flag = false;
+ dwin_zoffset = TERN0(HAS_BED_PROBE, probe.offset.z);
+ if (dwin_abort_flag) {
+ planner.finish_and_disable();
+ }
+ HMI_ReturnScreen();
+}
+
+void DWIN_MeshLevelingStart() {
+ #if HAS_ONESTEP_LEVELING
+ HMI_SaveProcessID(Leveling);
+ DWIN_Draw_Popup(ICON_AutoLeveling, GET_TEXT(MSG_BED_LEVELING), "Please wait until done.");
+ #elif ENABLED(MESH_BED_LEVELING)
+ Draw_ManualMesh_Menu();
+ #endif
+}
+
+void DWIN_CompletedLeveling() { HMI_ReturnScreen(); }
+
+#if HAS_MESH
+ void DWIN_MeshUpdate(const int8_t xpos, const int8_t ypos, const float zval) {
+ char msg[33] = "";
+ char str_1[6] = "";
+ sprintf_P(msg, PSTR(S_FMT " %i/%i Z=%s"), GET_TEXT(MSG_PROBING_POINT), xpos, ypos,
+ dtostrf(zval, 1, 2, str_1));
+ ui.set_status(msg);
+ }
+#endif
+
+// PID process
+void DWIN_PidTuning(pidresult_t result) {
+ switch (result) {
+ case PID_BED_START:
+ HMI_SaveProcessID(NothingToDo);
+ DWIN_Draw_Popup(ICON_TempTooHigh, GET_TEXT(MSG_PID_AUTOTUNE), "for BED is running.");
+ break;
+ case PID_EXTR_START:
+ HMI_SaveProcessID(NothingToDo);
+ DWIN_Draw_Popup(ICON_TempTooHigh, GET_TEXT(MSG_PID_AUTOTUNE), "for Nozzle is running.");
+ break;
+ case PID_BAD_EXTRUDER_NUM:
+ checkkey = last_checkkey;
+ DWIN_Popup_Confirm(ICON_TempTooLow, "PID Autotune failed!", "Bad extruder");
+ break;
+ case PID_TUNING_TIMEOUT:
+ checkkey = last_checkkey;
+ DWIN_Popup_Confirm(ICON_TempTooHigh, "Error", GET_TEXT(MSG_PID_TIMEOUT));
+ break;
+ case PID_TEMP_TOO_HIGH:
+ checkkey = last_checkkey;
+ DWIN_Popup_Confirm(ICON_TempTooHigh, "PID Autotune failed!", "Temperature too high");
+ break;
+ case PID_DONE:
+ checkkey = last_checkkey;
+ DWIN_Popup_Confirm(ICON_TempTooLow, GET_TEXT(MSG_PID_AUTOTUNE), GET_TEXT(MSG_BUTTON_DONE));
+ break;
+ default:
+ checkkey = last_checkkey;
+ break;
+ }
+}
+
+// Update filename on print
+void DWIN_Print_Header(const char *text = nullptr) {
+
+ static char headertxt[31] = ""; // Print header text
+
+ if (text != nullptr) {
+ const int8_t size = _MIN((unsigned) 30, strlen_P(text));
+ LOOP_L_N(i, size) headertxt[i] = text[i];
+ headertxt[size] = '\0';
+ }
+ if (checkkey == PrintProcess || checkkey == PrintDone) {
+ DWIN_Draw_Rectangle(1, HMI_data.Background_Color, 0, 60, DWIN_WIDTH, 60+16);
+ DWINUI::Draw_CenteredString(60, headertxt);
+ }
+}
+
+void Draw_Title(TitleClass* title) {
+ DWIN_Draw_Rectangle(1, HMI_data.TitleBg_color, 0, 0, DWIN_WIDTH - 1, TITLE_HEIGHT - 1);
+ if (title->frameid)
+ DWIN_Frame_AreaCopy(title->frameid, title->frame.left, title->frame.top, title->frame.right, title->frame.bottom, 14, (TITLE_HEIGHT - (title->frame.bottom - title->frame.top)) / 2 - 1);
+ else
+ DWIN_Draw_String(false, false, DWIN_FONT_HEAD, HMI_data.TitleTxt_color, HMI_data.TitleBg_color, 14, (TITLE_HEIGHT - DWINUI::Get_font_height(DWIN_FONT_HEAD)) / 2 - 1, title->caption);
+}
+
+void Draw_Menu(MenuClass* menu) {
+ DWINUI::SetColors(HMI_data.Text_Color, HMI_data.Background_Color);
+ DWIN_Draw_Rectangle(1, DWINUI::backcolor, 0, TITLE_HEIGHT, DWIN_WIDTH - 1, STATUS_Y - 1);
+ ui.set_status("");
+}
+
+// Startup routines
+void DWIN_Startup() {
+ DWINUI::Init();
+ DWINUI::onCursorDraw = Draw_Menu_Cursor;
+ DWINUI::onCursorErase = Erase_Menu_Cursor;
+ DWINUI::onTitleDraw = Draw_Title;
+ DWINUI::onMenuDraw = Draw_Menu;
+ HMI_SetLanguage();
+}
+
+void DWIN_DrawStatusLine(const uint16_t color, const uint16_t bgcolor, const char *text) {
+ DWIN_Draw_Rectangle(1, bgcolor, 0, STATUS_Y, DWIN_WIDTH, STATUS_Y + 20);
+ if (text) DWINUI::Draw_CenteredString(color, STATUS_Y + 2, text);
+ DWIN_UpdateLCD();
+}
+
+// Update Status line
+void DWIN_StatusChanged(const char *text) {
+ DWIN_DrawStatusLine(HMI_data.StatusTxt_Color, HMI_data.StatusBg_Color, text);
+}
+
+void DWIN_StatusChanged_P(PGM_P const pstr) {
+ char str[strlen_P((const char*)pstr) + 1];
+ strcpy_P(str, (const char*)pstr);
+ DWIN_StatusChanged(str);
+}
+
+// Started a Print Job
+void DWIN_Print_Started(const bool sd) {
+ sdprint = card.isPrinting() || sd;
+ _percent_done = 0;
+ _remain_time = 0;
+ HMI_flag.print_finish = false;
+ Goto_PrintProcess();
+}
+
+// Ended print job
+void DWIN_Print_Finished() {
+ if (checkkey == PrintProcess || printingIsActive()) {
+ thermalManager.disable_all_heaters();
+ thermalManager.zero_fan_speeds();
+ HMI_flag.print_finish = true;
+ }
+}
+
+// Progress Bar update
+void DWIN_Progress_Update() {
+ if (parser.seenval('P')) _percent_done = parser.byteval('P');
+ if (parser.seenval('R')) _remain_time = parser.ulongval('R') * 60;
+ if (checkkey == PrintProcess) {
+ Draw_Print_ProgressBar();
+ Draw_Print_ProgressRemain();
+ Draw_Print_ProgressElapsed();
+ }
+}
+
+#if HAS_FILAMENT_SENSOR
+ // Filament Runout process
+ void DWIN_FilamentRunout(const uint8_t extruder) { ui.set_status_P(GET_TEXT(MSG_RUNOUT_SENSOR)); }
+#endif
+
+void DWIN_SetColorDefaults() {
+ HMI_data.Background_Color = Def_Background_Color;
+ HMI_data.Cursor_color = Def_Cursor_color;
+ HMI_data.TitleBg_color = Def_TitleBg_color;
+ HMI_data.TitleTxt_color = Def_TitleTxt_color;
+ HMI_data.Text_Color = Def_Text_Color;
+ HMI_data.Selected_Color = Def_Selected_Color;
+ HMI_data.SplitLine_Color = Def_SplitLine_Color;
+ HMI_data.Highlight_Color = Def_Highlight_Color;
+ HMI_data.StatusBg_Color = Def_StatusBg_Color;
+ HMI_data.StatusTxt_Color = Def_StatusTxt_Color;
+ HMI_data.PopupBg_color = Def_PopupBg_color;
+ HMI_data.PopupTxt_Color = Def_PopupTxt_Color;
+ HMI_data.AlertBg_Color = Def_AlertBg_Color;
+ HMI_data.AlertTxt_Color = Def_AlertTxt_Color;
+ HMI_data.PercentTxt_Color = Def_PercentTxt_Color;
+ HMI_data.Barfill_Color = Def_Barfill_Color;
+ HMI_data.Indicator_Color = Def_Indicator_Color;
+ HMI_data.Coordinate_Color = Def_Coordinate_Color;
+}
+
+void DWIN_SetDataDefaults() {
+ DWIN_SetColorDefaults();
+ DWINUI::SetColors(HMI_data.Text_Color, HMI_data.Background_Color);
+ TERN_(HAS_HOTEND, HMI_data.HotendPidT = PREHEAT_1_TEMP_HOTEND);
+ TERN_(HAS_HEATED_BED, HMI_data.BedPidT = PREHEAT_1_TEMP_BED);
+ TERN_(HAS_HOTEND, HMI_data.PidCycles = 5);
+ TERN_(PREVENT_COLD_EXTRUSION, HMI_data.ExtMinT = EXTRUDE_MINTEMP);
+}
+
+void DWIN_StoreSettings(char *buff) {
+ memcpy(buff, &HMI_data, _MIN(sizeof(HMI_data), eeprom_data_size));
+}
+
+void DWIN_LoadSettings(const char *buff) {
+ memcpy(&HMI_data, buff, _MIN(sizeof(HMI_data), eeprom_data_size));
+ dwin_zoffset = TERN0(HAS_BED_PROBE, probe.offset.z);
+ if (HMI_data.Text_Color == HMI_data.Background_Color) DWIN_SetColorDefaults();
+ DWINUI::SetColors(HMI_data.Text_Color, HMI_data.Background_Color);
+ TERN_(PREVENT_COLD_EXTRUSION, ApplyExtMinT());
+ feedrate_percentage = 100;
+}
+
+void MarlinUI::kill_screen(PGM_P lcd_error, PGM_P lcd_component) {
+ DWIN_Draw_Popup(ICON_BLTouch, lcd_error, lcd_component);
+ DWIN_UpdateLCD();
+}
+
+void DWIN_RebootScreen() {
+ DWIN_Frame_Clear(Color_Bg_Black);
+ DWINUI::Draw_Icon(ICON_LOGO, 71, 150); // CREALITY logo
+ DWINUI::Draw_CenteredString(Color_White, 200, F("Please wait until reboot."));
+ DWIN_UpdateLCD();
+ delay(500);
+}
+
+void DWIN_Redraw_screen() {
+ Draw_Main_Area();
+ DWIN_StatusChanged(ui.status_message);
+ Draw_Status_Area(false);
+}
+
+#if ENABLED(ADVANCED_PAUSE_FEATURE)
+
+ void DWIN_Popup_Pause(const char *msg, uint8_t button = 0) {
+ HMI_SaveProcessID(button ? WaitResponse : NothingToDo);
+ DWIN_Draw_Popup(ICON_BLTouch, "Advanced Pause", msg, button);
+ ui.reset_status(true);
+ }
+
+ void MarlinUI::pause_show_message(const PauseMessage message, const PauseMode mode/*=PAUSE_MODE_SAME*/, const uint8_t extruder/*=active_extruder*/) {
+ switch (message) {
+ case PAUSE_MESSAGE_PARKING: DWIN_Popup_Pause(GET_TEXT(MSG_PAUSE_PRINT_PARKING)); break;
+ case PAUSE_MESSAGE_CHANGING: DWIN_Popup_Pause(GET_TEXT(MSG_FILAMENT_CHANGE_INIT)); break;
+ case PAUSE_MESSAGE_UNLOAD: DWIN_Popup_Pause(GET_TEXT(MSG_FILAMENT_CHANGE_UNLOAD)); break;
+ case PAUSE_MESSAGE_WAITING: DWIN_Popup_Pause(GET_TEXT(MSG_ADVANCED_PAUSE_WAITING), ICON_Continue_E); break;
+ case PAUSE_MESSAGE_INSERT: DWIN_Popup_Continue(ICON_BLTouch, "Advanced Pause", GET_TEXT(MSG_FILAMENT_CHANGE_INSERT)); break;
+ case PAUSE_MESSAGE_LOAD: DWIN_Popup_Pause(GET_TEXT(MSG_FILAMENT_CHANGE_LOAD)); break;
+ case PAUSE_MESSAGE_PURGE: DWIN_Popup_Pause(GET_TEXT(MSG_FILAMENT_CHANGE_PURGE)); break;
+ case PAUSE_MESSAGE_OPTION: DWIN_Popup_FilamentPurge(); break;
+ case PAUSE_MESSAGE_RESUME: DWIN_Popup_Pause(GET_TEXT(MSG_FILAMENT_CHANGE_RESUME)); break;
+ case PAUSE_MESSAGE_HEAT: DWIN_Popup_Pause(GET_TEXT(MSG_FILAMENT_CHANGE_HEAT), ICON_Continue_E); break;
+ case PAUSE_MESSAGE_HEATING: ui.set_status_P(GET_TEXT(MSG_FILAMENT_CHANGE_HEATING)); break;
+ case PAUSE_MESSAGE_STATUS: HMI_ReturnScreen(); break;
+ default: break;
+ }
+ }
+
+ void Draw_Popup_FilamentPurge() {
+ DWIN_Draw_Popup(ICON_BLTouch, "Advanced Pause", "Purge or Continue?");
+ DWINUI::Draw_Icon(ICON_Confirm_E, 26, 280);
+ DWINUI::Draw_Icon(ICON_Continue_E, 146, 280);
+ Draw_Select_Highlight(true);
+ }
+
+ // Handle responses such as:
+ // - Purge More, Continue
+ // - General "Continue" response
+ void DWIN_Popup_FilamentPurge() {
+ HMI_SaveProcessID(FilamentPurge);
+ pause_menu_response = PAUSE_RESPONSE_WAIT_FOR;
+ Draw_Popup_FilamentPurge();
+ }
+
+ void HMI_FilamentPurge() {
+ ENCODER_DiffState encoder_diffState = get_encoder_state();
+ if (encoder_diffState == ENCODER_DIFF_NO) return;
+ if (encoder_diffState == ENCODER_DIFF_CW)
+ Draw_Select_Highlight(false);
+ else if (encoder_diffState == ENCODER_DIFF_CCW)
+ Draw_Select_Highlight(true);
+ else if (encoder_diffState == ENCODER_DIFF_ENTER) {
+ if (HMI_flag.select_flag)
+ pause_menu_response = PAUSE_RESPONSE_EXTRUDE_MORE; // "Purge More" button
+ else {
+ HMI_SaveProcessID(NothingToDo);
+ pause_menu_response = PAUSE_RESPONSE_RESUME_PRINT; // "Continue" button
+ }
+ }
+ DWIN_UpdateLCD();
+ }
+
+#endif // ADVANCED_PAUSE_FEATURE
+
+void HMI_LockScreen() {
+ ENCODER_DiffState encoder_diffState = get_encoder_state();
+ if (encoder_diffState == ENCODER_DIFF_NO) return;
+ LockScreen.onEncoderState(encoder_diffState);
+ if (LockScreen.isUnlocked()) {
+ if (CurrentMenu == AdvancedSettings)
+ Draw_AdvancedSettings_Menu();
+ else
+ Draw_Tune_Menu();
+ }
+}
+
+void DWIN_LockScreen(const bool flag) {
+ HMI_flag.lock_flag = flag;
+ checkkey = Locked;
+ LockScreen.Init();
+}
+
+//
+// NEW MENU SUBSYSTEM =========================================================
+//
+
+// On click functions
+
+// Generic onclick event without draw anything
+// process: process id HMI destiny
+// lo: low limit
+// hi: high limit
+// dp: decimal places, 0 for integers
+// val: value / scaled value
+// LiveUpdate: live update function when the encoder changes
+// Apply: update function when the encoder is pressed
+void SetOnClick(uint8_t process, const int32_t lo, const int32_t hi, uint8_t dp, const int32_t val, void (*Apply)() = nullptr, void (*LiveUpdate)() = nullptr) {
+ last_checkkey = Menu;
+ checkkey = process;
+ HMI_value.MinValue = lo;
+ HMI_value.MaxValue = hi;
+ HMI_value.dp = dp;
+ HMI_value.Apply = Apply;
+ HMI_value.LiveUpdate = LiveUpdate;
+ HMI_value.Value = val;
+ EncoderRate.enabled = true;
+}
+
+// Generic onclick event for integer values
+// process: process id HMI destiny
+// lo: scaled low limit
+// hi: scaled high limit
+// val: value
+// LiveUpdate: live update function when the encoder changes
+// Apply: update function when the encoder is pressed
+void SetValueOnClick(uint8_t process, const int32_t lo, const int32_t hi, const int32_t val, void (*Apply)() = nullptr, void (*LiveUpdate)() = nullptr) {
+ SetOnClick(process, lo, hi, 0, val, Apply, LiveUpdate);
+ Draw_Menu_IntValue(HMI_data.Selected_Color, CurrentMenu->line(), 4, HMI_value.Value);
+}
+
+// Generic onclick event for float values
+// process: process id HMI destiny
+// lo: scaled low limit
+// hi: scaled high limit
+// val: value
+// LiveUpdate: live update function when the encoder changes
+// Apply: update function when the encoder is pressed
+void SetValueOnClick(uint8_t process, const float lo, const float hi, uint8_t dp, const float val, void (*Apply)() = nullptr, void (*LiveUpdate)() = nullptr) {
+ const int32_t value = round(val * POW(10, dp));
+ SetOnClick(process, lo * POW(10, dp), hi * POW(10, dp), dp, value, Apply, LiveUpdate);
+ DWINUI::Draw_Signed_Float(HMI_data.Text_Color, HMI_data.Selected_Color, 3, dp, VALX - dp * DWINUI::Get_font_width(DWIN_FONT_MENU), MBASE(CurrentMenu->line()), val);
+}
+
+// Generic onclick event for integer values
+// lo: scaled low limit
+// hi: scaled high limit
+// val: value
+// LiveUpdate: live update function when the encoder changes
+// Apply: update function when the encoder is pressed
+inline void SetIntOnClick(const int32_t lo, const int32_t hi, const int32_t val, void (*Apply)() = nullptr, void (*LiveUpdate)() = nullptr) {
+ SetValueOnClick(SetInt, lo, hi, val, Apply, LiveUpdate);
+}
+
+// Generic onclick event for set pointer to 16 bit uinteger values
+// lo: low limit
+// hi: high limit
+// LiveUpdate: live update function when the encoder changes
+// Apply: update function when the encoder is pressed
+void SetPIntOnClick(const int32_t lo, const int32_t hi, void (*Apply)() = nullptr, void (*LiveUpdate)() = nullptr) {
+ HMI_value.P_Int = (int16_t*)static_cast(CurrentMenu->SelectedItem())->value;
+ const int32_t value = *HMI_value.P_Int;
+ SetValueOnClick(SetPInt, lo, hi, value, Apply, LiveUpdate);
+}
+
+// Generic onclick event for float values
+// process: process id HMI destiny
+// lo: low limit
+// hi: high limit
+// dp: decimal places
+// val: value
+inline void SetFloatOnClick(const float lo, const float hi, uint8_t dp, const float val, void (*Apply)() = nullptr, void (*LiveUpdate)() = nullptr) {
+ SetValueOnClick(SetFloat, lo, hi, dp, val, Apply, LiveUpdate);
+}
+
+// Generic onclick event for set pointer to float values
+// lo: low limit
+// hi: high limit
+// LiveUpdate: live update function when the encoder changes
+// Apply: update function when the encoder is pressed
+void SetPFloatOnClick(const float lo, const float hi, uint8_t dp, void (*Apply)() = nullptr, void (*LiveUpdate)() = nullptr) {
+ HMI_value.P_Float = (float*)static_cast(CurrentMenu->SelectedItem())->value;
+ SetValueOnClick(SetPFloat, lo, hi, dp, *HMI_value.P_Float, Apply, LiveUpdate);
+}
+
+#if ENABLED(EEPROM_SETTINGS)
+ void WriteEeprom() {
+ const bool success = settings.save();
+ HMI_AudioFeedback(success);
+ }
+
+ void ReadEeprom() {
+ const bool success = settings.load();
+ DWIN_Redraw_screen();
+ HMI_AudioFeedback(success);
+ }
+
+ void ResetEeprom() {
+ settings.reset();
+ DWIN_Redraw_screen();
+ HMI_AudioFeedback();
+ }
+#endif
+
+// Reset Printer
+void RebootPrinter() {
+ dwin_abort_flag = true;
+ wait_for_heatup = wait_for_user = false; // Stop waiting for heating/user
+ thermalManager.disable_all_heaters();
+ planner.finish_and_disable();
+ DWIN_RebootScreen();
+ HAL_reboot();
+}
+
+void Goto_InfoMenu(){
+ checkkey = Info;
+ Draw_Info_Menu();
+}
+
+void DisableMotors() {
+ queue.inject_P(PSTR("M84"));
+}
+
+void AutoHome() {
+ queue.inject_P(G28_STR);
+}
+
+void SetHome() {
+ // Apply workspace offset, making the current position 0,0,0
+ queue.inject_P(PSTR("G92 X0 Y0 Z0"));
+ HMI_AudioFeedback();
+}
+
+#if HAS_ZOFFSET_ITEM
+ bool printer_busy() { return planner.movesplanned() || printingIsActive(); }
+ void ApplyZOffset() { TERN_(EEPROM_SETTINGS, settings.save()); }
+ void LiveZOffset() {
+ last_zoffset = dwin_zoffset;
+ dwin_zoffset = HMI_value.Value / 100.0f;
+ #if EITHER(BABYSTEP_ZPROBE_OFFSET, JUST_BABYSTEP)
+ if (BABYSTEP_ALLOWED()) babystep.add_mm(Z_AXIS, dwin_zoffset - last_zoffset);
+ #endif
+ }
+ #if EITHER(HAS_BED_PROBE, BABYSTEPPING)
+ void SetZOffset() { SetPFloatOnClick(Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX, 2, ApplyZOffset, LiveZOffset); }
+ #endif
+#endif
+
+#if HAS_PREHEAT
+ void SetPreheat(const uint8_t i) {
+ TERN_(HAS_HOTEND, thermalManager.setTargetHotend(ui.material_preset[i].hotend_temp, 0));
+ TERN_(HAS_HEATED_BED, thermalManager.setTargetBed(ui.material_preset[i].bed_temp));
+ TERN_(HAS_FAN, thermalManager.set_fan_speed(0, ui.material_preset[i].fan_speed));
+ }
+ void SetPreheat0() { SetPreheat(0); }
+ void SetPreheat1() { SetPreheat(1); }
+ void SetPreheat2() { SetPreheat(2); }
+
+ void SetCoolDown() {
+ TERN_(HAS_FAN, thermalManager.zero_fan_speeds());
+ #if HAS_HOTEND || HAS_HEATED_BED
+ thermalManager.disable_all_heaters();
+ #endif
+ }
+#endif
+
+void SetLanguage() {
+ HMI_ToggleLanguage();
+ CurrentMenu = nullptr; // Invalidate menu to full redraw
+ Draw_Prepare_Menu();
+}
+
+void LiveMove() {
+ *HMI_value.P_Float = HMI_value.Value / MINUNITMULT;
+ if (!planner.is_full()) {
+ planner.synchronize();
+ planner.buffer_line(current_position, homing_feedrate(HMI_value.axis));
+ }
+}
+void ApplyMoveE() {
+ last_E = HMI_value.Value / MINUNITMULT;
+ if (!planner.is_full()) {
+ planner.synchronize();
+ planner.buffer_line(current_position, MMM_TO_MMS(FEEDRATE_E));
+ }
+}
+void SetMoveX() { HMI_value.axis = X_AXIS; SetPFloatOnClick(X_MIN_POS, X_MAX_POS, UNITFDIGITS, planner.synchronize, LiveMove);}
+void SetMoveY() { HMI_value.axis = Y_AXIS; SetPFloatOnClick(Y_MIN_POS, Y_MAX_POS, UNITFDIGITS, planner.synchronize, LiveMove);}
+void SetMoveZ() { HMI_value.axis = Z_AXIS; SetPFloatOnClick(Z_MIN_POS, Z_MAX_POS, UNITFDIGITS, planner.synchronize, LiveMove);}
+
+#if HAS_HOTEND
+ void SetMoveE() {
+ #if ENABLED(PREVENT_COLD_EXTRUSION)
+ if (thermalManager.tooColdToExtrude(0)) {
+ Popup_Window_ETempTooLow();
+ return;
+ }
+ #endif
+ SetPFloatOnClick(last_E - (EXTRUDE_MAXLENGTH), last_E + (EXTRUDE_MAXLENGTH), UNITFDIGITS, ApplyMoveE);
+ }
+#endif
+
+void SetMoveZto0() {
+ char cmd[48] = "";
+ char str_1[5] = "", str_2[5] = "";
+ sprintf_P(cmd, PSTR("G28OXY\nG28Z\nG0X%sY%sF5000\nG0Z0F300"),
+ #if ENABLED(MESH_BED_LEVELING)
+ dtostrf(0, 1, 1, str_1),
+ dtostrf(0, 1, 1, str_2)
+ #else
+ dtostrf(X_CENTER, 1, 1, str_1),
+ dtostrf(Y_CENTER, 1, 1, str_2)
+ #endif
+ );
+ gcode.process_subcommands_now_P(cmd);
+ planner.synchronize();
+ ui.set_status_P(PSTR("Now adjust Z Offset"));
+ HMI_AudioFeedback(true);
+}
+
+void SetPID(celsius_t t, heater_id_t h) {
+ char cmd[48] = "";
+ char str_1[5] = "", str_2[5] = "";
+ sprintf_P(cmd, PSTR("G28OXY\nG0Z5F300\nG0X%sY%sF5000\nM84"),
+ dtostrf(X_CENTER, 1, 1, str_1),
+ dtostrf(Y_CENTER, 1, 1, str_2)
+ );
+ gcode.process_subcommands_now_P(cmd);
+ planner.synchronize();
+ thermalManager.PID_autotune(t, h, HMI_data.PidCycles, true);
+}
+#if HAS_HOTEND
+ void HotendPID() { SetPID(HMI_data.HotendPidT, H_E0); }
+#endif
+#if HAS_HEATED_BED
+ void BedPID() { SetPID(HMI_data.BedPidT, H_BED); }
+#endif
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+ void SetPwrLossr() {
+ recovery.enable(!recovery.enabled);
+ Draw_Chkb_Line(CurrentMenu->line(), recovery.enabled);
+ DWIN_UpdateLCD();
+ }
+#endif
+
+#if HAS_LCD_BRIGHTNESS
+ void ApplyBrightness() { ui.set_brightness(HMI_value.Value); }
+ void LiveBrightness() { DWIN_LCD_Brightness(HMI_value.Value); }
+ void SetBrightness() { SetIntOnClick(MIN_LCD_BRIGHTNESS, MAX_LCD_BRIGHTNESS, ui.brightness, ApplyBrightness, LiveBrightness); }
+#endif
+
+#if ENABLED(SOUND_MENU_ITEM)
+ void SetEnableSound() {
+ ui.buzzer_enabled = !ui.buzzer_enabled;
+ Draw_Chkb_Line(CurrentMenu->line(), ui.buzzer_enabled);
+ DWIN_UpdateLCD();
+ }
+#endif
+
+void Goto_LockScreen() {
+ DWIN_LockScreen(true);
+}
+
+#if HAS_HOME_OFFSET
+ void ApplyHomeOffset() { set_home_offset(HMI_value.axis, HMI_value.Value / MINUNITMULT); }
+ void SetHomeOffsetX() { HMI_value.axis = X_AXIS; SetPFloatOnClick(-50, 50, UNITFDIGITS, ApplyHomeOffset); }
+ void SetHomeOffsetY() { HMI_value.axis = Y_AXIS; SetPFloatOnClick(-50, 50, UNITFDIGITS, ApplyHomeOffset); }
+ void SetHomeOffsetZ() { HMI_value.axis = Z_AXIS; SetPFloatOnClick( -2, 2, UNITFDIGITS, ApplyHomeOffset); }
+#endif
+
+#if HAS_BED_PROBE
+ void SetProbeOffsetX() { SetPFloatOnClick(-50, 50, UNITFDIGITS); }
+ void SetProbeOffsetY() { SetPFloatOnClick(-50, 50, UNITFDIGITS); }
+ void SetProbeOffsetZ() { SetPFloatOnClick(-10, 10, 2); }
+ void ProbeTest() {
+ ui.set_status_P(GET_TEXT(MSG_M48_TEST));
+ queue.inject_P(PSTR("G28O\nM48 P10"));
+ }
+#endif
+
+#if HAS_FILAMENT_SENSOR
+ void SetRunoutEnable() {
+ runout.reset();
+ runout.enabled = !runout.enabled;
+ Draw_Chkb_Line(CurrentMenu->line(), runout.enabled);
+ DWIN_UpdateLCD();
+ }
+ #if HAS_FILAMENT_RUNOUT_DISTANCE
+ void ApplyRunoutDistance() { runout.set_runout_distance(HMI_value.Value / MINUNITMULT); }
+ void SetRunoutDistance() { SetFloatOnClick(0, 999, UNITFDIGITS, runout.runout_distance(), ApplyRunoutDistance); }
+ #endif
+#endif
+
+#if ENABLED(ADVANCED_PAUSE_FEATURE)
+ void SetFilLoad() { SetPFloatOnClick(0, MAX_LOAD_UNLOAD, UNITFDIGITS); }
+ void SetFilUnload() { SetPFloatOnClick(0, MAX_LOAD_UNLOAD, UNITFDIGITS); }
+#endif
+
+#if ENABLED(PREVENT_COLD_EXTRUSION)
+ void ApplyExtMinT() { thermalManager.extrude_min_temp = HMI_data.ExtMinT; thermalManager.allow_cold_extrude = (HMI_data.ExtMinT == 0); }
+ void SetExtMinT() { SetPIntOnClick(MIN_ETEMP, MAX_ETEMP, ApplyExtMinT); }
+#endif
+
+void RestoreDefaultsColors() {
+ DWIN_SetColorDefaults();
+ DWINUI::SetColors(HMI_data.Text_Color, HMI_data.Background_Color);
+ DWIN_Redraw_screen();
+}
+
+void SelColor() {
+ HMI_value.P_Int = (int16_t*)static_cast(CurrentMenu->SelectedItem())->value;
+ HMI_value.Color[0] = GetRColor(*HMI_value.P_Int); // Red
+ HMI_value.Color[1] = GetGColor(*HMI_value.P_Int); // Green
+ HMI_value.Color[2] = GetBColor(*HMI_value.P_Int); // Blue
+ Draw_GetColor_Menu();
+}
+
+void LiveRGBColor() {
+ HMI_value.Color[CurrentMenu->line() - 2] = HMI_value.Value;
+ uint16_t color = RGB(HMI_value.Color[0], HMI_value.Color[1], HMI_value.Color[2]);
+ DWIN_Draw_Rectangle(1, color, 20, 315, DWIN_WIDTH - 20, 335);
+}
+void SetRGBColor() {
+ const uint8_t color = CurrentMenu->SelectedItem()->icon;
+ SetIntOnClick(0, (color == 1) ? 63 : 31, HMI_value.Color[color], nullptr, LiveRGBColor);
+}
+
+void DWIN_ApplyColor() {
+ *HMI_value.P_Int = RGB(HMI_value.Color[0], HMI_value.Color[1], HMI_value.Color[2]);
+ DWINUI::SetColors(HMI_data.Text_Color, HMI_data.Background_Color);
+ Draw_Status_Area(false);
+ Draw_SelectColors_Menu();
+ ui.set_status_P(PSTR("Colors applied"));
+}
+
+void SetSpeed() { SetPIntOnClick(MIN_PRINT_SPEED, MAX_PRINT_SPEED); }
+
+#if HAS_HOTEND
+ void ApplyHotendTemp() { thermalManager.setTargetHotend(HMI_value.Value, 0); }
+ void SetHotendTemp() { SetIntOnClick(MIN_ETEMP, MAX_ETEMP, thermalManager.degTargetHotend(0), ApplyHotendTemp); }
+#endif
+
+#if HAS_HEATED_BED
+ void ApplyBedTemp() { thermalManager.setTargetBed(HMI_value.Value); }
+ void SetBedTemp() { SetIntOnClick(BED_MINTEMP, BED_MAX_TARGET, thermalManager.degTargetBed(), ApplyBedTemp); }
+#endif
+
+#if HAS_FAN
+ void ApplyFanSpeed() { thermalManager.set_fan_speed(0, HMI_value.Value); }
+ void SetFanSpeed() { SetIntOnClick(0, 255, thermalManager.fan_speed[0], ApplyFanSpeed); }
+#endif
+
+#if ENABLED(ADVANCED_PAUSE_FEATURE)
+ void ChangeFilament() {
+ HMI_SaveProcessID(NothingToDo);
+ queue.inject_P(PSTR("M600 B2"));
+ }
+
+ void ParkHead(){
+ ui.set_status_P(GET_TEXT(MSG_FILAMENT_PARK_ENABLED));
+ queue.inject_P(PSTR("G28O\nG27"));
+ }
+
+ #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ void UnloadFilament(){
+ ui.set_status_P(GET_TEXT(MSG_FILAMENTUNLOAD));
+ queue.inject_P(PSTR("M702 Z20"));
+ }
+
+ void LoadFilament(){
+ ui.set_status_P(GET_TEXT(MSG_FILAMENTLOAD));
+ queue.inject_P(PSTR("M701 Z20"));
+ }
+ #endif
+#endif
+
+void SetFlow() { SetPIntOnClick(MIN_PRINT_FLOW, MAX_PRINT_FLOW); }
+
+// Leveling Bed Corners
+void LevBed(uint8_t point) {
+ char cmd[100] = "";
+ #if HAS_ONESTEP_LEVELING
+ char str_1[6] = "", str_2[6] = "", str_3[6] = "";
+ #define fmt "X:%s, Y:%s, Z:%s"
+ float xpos = 0, ypos = 0, zval = 0;
+ float margin = PROBING_MARGIN;
+ #else
+ #define fmt "M420 S0\nG28O\nG90\nG0 Z5 F300\nG0 X%i Y%i F5000\nG0 Z0 F300"
+ int16_t xpos = 0, ypos = 0;
+ int16_t margin = 30;
+ #endif
+
+ switch (point) {
+ case 0:
+ ui.set_status_P(GET_TEXT(MSG_LEVBED_FL));
+ xpos = ypos = margin;
+ break;
+ case 1:
+ ui.set_status_P(GET_TEXT(MSG_LEVBED_FR));
+ xpos = X_BED_SIZE - margin; ypos = margin;
+ break;
+ case 2:
+ ui.set_status_P(GET_TEXT(MSG_LEVBED_BR));
+ xpos = X_BED_SIZE - margin; ypos = Y_BED_SIZE - margin;
+ break;
+ case 3:
+ ui.set_status_P(GET_TEXT(MSG_LEVBED_BL));
+ xpos = margin; ypos = Y_BED_SIZE - margin;
+ break;
+ case 4:
+ ui.set_status_P(GET_TEXT(MSG_LEVBED_C));
+ xpos = X_BED_SIZE / 2; ypos = Y_BED_SIZE / 2;
+ break;
+ }
+
+ #if HAS_ONESTEP_LEVELING
+ planner.synchronize();
+ gcode.process_subcommands_now_P(PSTR("M420S0\nG28O"));
+ planner.synchronize();
+ zval = probe.probe_at_point(xpos, ypos, PROBE_PT_STOW);
+ sprintf_P(cmd, PSTR(fmt),
+ dtostrf(xpos, 1, 1, str_1),
+ dtostrf(ypos, 1, 1, str_2),
+ dtostrf(zval, 1, 2, str_3)
+ );
+ ui.set_status_P(cmd);
+ #else
+ planner.synchronize();
+ sprintf_P(cmd, PSTR(fmt), xpos, ypos);
+ queue.inject(cmd);
+ #endif
+}
+
+void LevBedFL() { LevBed(0); }
+void LevBedFR() { LevBed(1); }
+void LevBedBR() { LevBed(2); }
+void LevBedBL() { LevBed(3); }
+void LevBedC () { LevBed(4); }
+
+#if ENABLED(MESH_BED_LEVELING)
+ void ManualMeshStart(){
+ ui.set_status_P(GET_TEXT(MSG_UBL_BUILD_MESH_MENU));
+ gcode.process_subcommands_now_P(PSTR("G28 XYO\nG28 Z\nM211 S0\nG29S1"));
+ planner.synchronize();
+ #ifdef MANUAL_PROBE_START_Z
+ const uint8_t line = CurrentMenu->line(MMeshMoveZItem->pos);
+ DWINUI::Draw_Signed_Float(HMI_data.Text_Color, HMI_data.Background_Color, 3, 2, VALX - 2 * DWINUI::Get_font_width(DWIN_FONT_MENU), MBASE(line), MANUAL_PROBE_START_Z);
+ #endif
+ }
+
+ void LiveMeshMoveZ() {
+ *HMI_value.P_Float = HMI_value.Value / POW(10, 2);
+ if (!planner.is_full()) {
+ planner.synchronize();
+ planner.buffer_line(current_position, homing_feedrate(Z_AXIS));
+ }
+ }
+ void SetMMeshMoveZ() { SetPFloatOnClick(-1, 1, 2, planner.synchronize, LiveMeshMoveZ);}
+
+ void ManualMeshContinue(){
+ gcode.process_subcommands_now_P(PSTR("G29S2"));
+ planner.synchronize();
+ MMeshMoveZItem->Draw(CurrentMenu->line(MMeshMoveZItem->pos));
+ }
+
+ void ManualMeshSave(){
+ ui.set_status_P(GET_TEXT(MSG_UBL_STORAGE_MESH_MENU));
+ queue.inject_P(PSTR("M211 S1\nM500"));
+ }
+#endif
+
+#if HAS_PREHEAT
+ #if HAS_HOTEND
+ void SetPreheatEndTemp() { SetPIntOnClick(MIN_ETEMP, MAX_ETEMP); }
+ #endif
+ #if HAS_HEATED_BED
+ void SetPreheatBedTemp() { SetPIntOnClick(BED_MINTEMP, BED_MAX_TARGET); }
+ #endif
+ #if HAS_FAN
+ void SetPreheatFanSpeed() { SetPIntOnClick(0, 255); }
+ #endif
+#endif
+
+void ApplyMaxSpeed() { planner.set_max_feedrate(HMI_value.axis, HMI_value.Value / MINUNITMULT); }
+void SetMaxSpeedX() { HMI_value.axis = X_AXIS, SetFloatOnClick(MIN_MAXFEEDSPEED, default_max_feedrate[X_AXIS] * 2, UNITFDIGITS, planner.settings.max_feedrate_mm_s[X_AXIS], ApplyMaxSpeed); }
+void SetMaxSpeedY() { HMI_value.axis = Y_AXIS, SetFloatOnClick(MIN_MAXFEEDSPEED, default_max_feedrate[Y_AXIS] * 2, UNITFDIGITS, planner.settings.max_feedrate_mm_s[Y_AXIS], ApplyMaxSpeed); }
+void SetMaxSpeedZ() { HMI_value.axis = Z_AXIS, SetFloatOnClick(MIN_MAXFEEDSPEED, default_max_feedrate[Z_AXIS] * 2, UNITFDIGITS, planner.settings.max_feedrate_mm_s[Z_AXIS], ApplyMaxSpeed); }
+#if HAS_HOTEND
+ void SetMaxSpeedE() { HMI_value.axis = E_AXIS; SetFloatOnClick(MIN_MAXFEEDSPEED, default_max_feedrate[E_AXIS] * 2, UNITFDIGITS, planner.settings.max_feedrate_mm_s[E_AXIS], ApplyMaxSpeed); }
+#endif
+
+void ApplyMaxAccel() { planner.set_max_acceleration(HMI_value.axis, HMI_value.Value); }
+void SetMaxAccelX() { HMI_value.axis = X_AXIS, SetIntOnClick(MIN_MAXACCELERATION, default_max_acceleration[X_AXIS] * 2, planner.settings.max_acceleration_mm_per_s2[X_AXIS], ApplyMaxAccel); }
+void SetMaxAccelY() { HMI_value.axis = Y_AXIS, SetIntOnClick(MIN_MAXACCELERATION, default_max_acceleration[Y_AXIS] * 2, planner.settings.max_acceleration_mm_per_s2[Y_AXIS], ApplyMaxAccel); }
+void SetMaxAccelZ() { HMI_value.axis = Z_AXIS, SetIntOnClick(MIN_MAXACCELERATION, default_max_acceleration[Z_AXIS] * 2, planner.settings.max_acceleration_mm_per_s2[Z_AXIS], ApplyMaxAccel); }
+#if HAS_HOTEND
+ void SetMaxAccelE() { HMI_value.axis = E_AXIS; SetIntOnClick(MIN_MAXACCELERATION, default_max_acceleration[E_AXIS] * 2, planner.settings.max_acceleration_mm_per_s2[E_AXIS], ApplyMaxAccel); }
+#endif
+
+#if HAS_CLASSIC_JERK
+ void ApplyMaxJerk() { planner.set_max_jerk(HMI_value.axis, HMI_value.Value / MINUNITMULT); }
+ void SetMaxJerkX() { HMI_value.axis = X_AXIS, SetFloatOnClick(MIN_MAXJERK, default_max_jerk[X_AXIS] * 2, UNITFDIGITS, planner.max_jerk[X_AXIS], ApplyMaxJerk); }
+ void SetMaxJerkY() { HMI_value.axis = Y_AXIS, SetFloatOnClick(MIN_MAXJERK, default_max_jerk[Y_AXIS] * 2, UNITFDIGITS, planner.max_jerk[Y_AXIS], ApplyMaxJerk); }
+ void SetMaxJerkZ() { HMI_value.axis = Z_AXIS, SetFloatOnClick(MIN_MAXJERK, default_max_jerk[Z_AXIS] * 2, UNITFDIGITS, planner.max_jerk[Z_AXIS], ApplyMaxJerk); }
+ #if HAS_HOTEND
+ void SetMaxJerkE() { HMI_value.axis = E_AXIS; SetFloatOnClick(MIN_MAXJERK, default_max_jerk[E_AXIS] * 2, UNITFDIGITS, planner.max_jerk[E_AXIS], ApplyMaxJerk); }
+ #endif
+#endif
+
+void SetStepsX() { HMI_value.axis = X_AXIS, SetPFloatOnClick( MIN_STEP, MAX_STEP, UNITFDIGITS); }
+void SetStepsY() { HMI_value.axis = Y_AXIS, SetPFloatOnClick( MIN_STEP, MAX_STEP, UNITFDIGITS); }
+void SetStepsZ() { HMI_value.axis = Z_AXIS, SetPFloatOnClick( MIN_STEP, MAX_STEP, UNITFDIGITS); }
+#if HAS_HOTEND
+ void SetStepsE() { HMI_value.axis = E_AXIS; SetPFloatOnClick( MIN_STEP, MAX_STEP, UNITFDIGITS); }
+ void SetHotendPidT() { SetPIntOnClick(MIN_ETEMP, MAX_ETEMP); }
+#endif
+#if HAS_HEATED_BED
+ void SetBedPidT() { SetPIntOnClick(BED_MINTEMP, BED_MAX_TARGET); }
+#endif
+
+#if HAS_HOTEND || HAS_HEATED_BED
+ void SetPidCycles() { SetPIntOnClick(3, 50); }
+ void SetKp() { SetPFloatOnClick(0, 1000, 2); }
+ void ApplyPIDi() {
+ *HMI_value.P_Float = scalePID_i(HMI_value.Value / POW(10, 2));
+ thermalManager.updatePID();
+ }
+ void ApplyPIDd() {
+ *HMI_value.P_Float = scalePID_d(HMI_value.Value / POW(10, 2));
+ thermalManager.updatePID();
+ }
+ void SetKi() {
+ HMI_value.P_Float = (float*)static_cast(CurrentMenu->SelectedItem())->value;
+ const float value = unscalePID_i(*HMI_value.P_Float);
+ SetFloatOnClick(0, 1000, 2, value, ApplyPIDi);
+ }
+ void SetKd() {
+ HMI_value.P_Float = (float*)static_cast(CurrentMenu->SelectedItem())->value;
+ const float value = unscalePID_d(*HMI_value.P_Float);
+ SetFloatOnClick(0, 1000, 2, value, ApplyPIDd);
+ }
+#endif
+// Menuitem Drawing functions =================================================
+
+void onDrawMenuItem(MenuItemClass* menuitem, int8_t line) {
+ if (menuitem->icon) DWINUI::Draw_Icon(menuitem->icon, ICOX, MBASE(line) - 3);
+ if (menuitem->frameid)
+ DWIN_Frame_AreaCopy(menuitem->frameid, menuitem->frame.left, menuitem->frame.top, menuitem->frame.right, menuitem->frame.bottom, LBLX, MBASE(line));
+ else if (menuitem->caption)
+ DWINUI::Draw_String(LBLX, MBASE(line) - 1, menuitem->caption);
+ DWIN_Draw_HLine(HMI_data.SplitLine_Color, 16, MYPOS(line + 1), 240);
+}
+
+void onDrawSubMenu(MenuItemClass* menuitem, int8_t line) {
+ onDrawMenuItem(menuitem, line);
+ DWINUI::Draw_Icon(ICON_More, VALX + 16, MBASE(line) - 3);
+}
+
+void onDrawIntMenu(MenuItemClass* menuitem, int8_t line, uint16_t value) {
+ onDrawMenuItem(menuitem, line);
+ Draw_Menu_IntValue(HMI_data.Background_Color, line, 4, value);
+}
+
+void onDrawPIntMenu(MenuItemClass* menuitem, int8_t line) {
+ const uint16_t value = *(uint16_t*)static_cast(menuitem)->value;
+ onDrawIntMenu(menuitem, line, value);
+}
+
+void onDrawPInt8Menu(MenuItemClass* menuitem, int8_t line) {
+ const uint8_t value = *(uint8_t*)static_cast(menuitem)->value;
+ onDrawIntMenu(menuitem, line, value);
+}
+
+void onDrawPInt32Menu(MenuItemClass* menuitem, int8_t line) {
+ const uint32_t value = *(uint32_t*)static_cast(menuitem)->value;
+ onDrawIntMenu(menuitem, line, value);
+}
+
+void onDrawFloatMenu(MenuItemClass* menuitem, int8_t line, uint8_t dp, const float value) {
+ onDrawMenuItem(menuitem, line);
+ DWINUI::Draw_Signed_Float(HMI_data.Text_Color, HMI_data.Background_Color, 3, dp, VALX - dp * DWINUI::Get_font_width(DWIN_FONT_MENU), MBASE(line), value);
+}
+
+void onDrawPFloatMenu(MenuItemClass* menuitem, int8_t line) {
+ const float value = *(float*)static_cast(menuitem)->value;
+ const int8_t dp = UNITFDIGITS;
+ onDrawFloatMenu(menuitem, line, dp, value);
+}
+
+void onDrawPFloat2Menu(MenuItemClass* menuitem, int8_t line) {
+ const float value = *(float*)static_cast(menuitem)->value;
+ onDrawFloatMenu(menuitem, line, 2, value);
+}
+
+void onDrawChkbMenu(MenuItemClass* menuitem, int8_t line, bool checked) {
+ onDrawMenuItem(menuitem, line);
+ Draw_Chkb_Line(line, checked);
+}
+
+void onDrawBack(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 129, 72, 156, 84);
+ onDrawMenuItem(menuitem, line);
+}
+
+void onDrawTempSubMenu(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 57, 104, 84, 116);
+ onDrawSubMenu(menuitem, line);
+}
+
+void onDrawMotionSubMenu(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 87, 104, 114, 116);
+ onDrawSubMenu(menuitem, line);
+}
+
+#if ENABLED(EEPROM_SETTINGS)
+ void onDrawWriteEeprom(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 117, 104, 172, 116);
+ onDrawMenuItem(menuitem, line);
+ }
+
+ void onDrawReadEeprom(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 174, 103, 229, 116);
+ onDrawMenuItem(menuitem, line);
+ }
+
+ void onDrawResetEeprom(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 1, 118, 56, 131);
+ onDrawMenuItem(menuitem, line);
+ }
+#endif
+
+void onDrawInfoSubMenu(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 231, 104, 258, 116);
+ onDrawSubMenu(menuitem, line);
+}
+
+void onDrawMoveX(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 58, 118, 106, 132);
+ onDrawPFloatMenu(menuitem, line);
+}
+
+void onDrawMoveY(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 109, 118, 157, 132);
+ onDrawPFloatMenu(menuitem, line);
+}
+
+void onDrawMoveZ(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 160, 118, 209, 132);
+ onDrawPFloatMenu(menuitem, line);
+}
+
+#if HAS_HOTEND
+ void onDrawMoveE(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 212, 118, 253, 131);
+ onDrawPFloatMenu(menuitem, line);
+ }
+#endif
+
+void onDrawMoveSubMenu(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 159, 70, 200, 84);
+ onDrawSubMenu(menuitem, line);
+}
+
+void onDrawDisableMotors(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 204, 70, 259, 82);
+ onDrawMenuItem(menuitem, line);
+}
+
+void onDrawAutoHome(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 0, 89, 41, 101);
+ onDrawMenuItem(menuitem, line);
+}
+
+#if HAS_ZOFFSET_ITEM
+ #if EITHER(HAS_BED_PROBE, BABYSTEPPING)
+ void onDrawZOffset(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 174, 164, 223, 177);
+ onDrawPFloat2Menu(menuitem, line);
+ }
+ #else
+ void onDrawHomeOffset(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 43, 89, 98, 101);
+ onDrawMenuItem(menuitem, line);
+ }
+ #endif
+#endif
+
+#if HAS_HOTEND
+ void onDrawPreheat1(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 100, 89, 151, 101);
+ onDrawMenuItem(menuitem, line);
+ }
+ void onDrawPreheat2(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 180, 89, 233, 100);
+ onDrawMenuItem(menuitem, line);
+ }
+#endif
+
+#if HAS_PREHEAT
+ void onDrawCooldown(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 1, 104, 56, 117);
+ onDrawMenuItem(menuitem, line);
+ }
+#endif
+
+void onDrawLanguage(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 239, 134, 266, 146);
+ onDrawMenuItem(menuitem, line);
+ DWINUI::Draw_String(VALX, MBASE(line), HMI_IsChinese() ? F("CN") : F("EN"));
+}
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+ void onDrawPwrLossR(MenuItemClass* menuitem, int8_t line) { onDrawChkbMenu(menuitem, line, recovery.enabled); }
+#endif
+
+void onDrawEnableSound(MenuItemClass* menuitem, int8_t line) { onDrawChkbMenu(menuitem, line, ui.buzzer_enabled); }
+
+void onDrawSelColorItem(MenuItemClass* menuitem, int8_t line) {
+ const uint16_t color = *(uint16_t*)static_cast(menuitem)->value;
+ DWIN_Draw_Rectangle(0, HMI_data.Highlight_Color, ICOX + 1, MBASE(line) - 1 + 1, ICOX + 18, MBASE(line) - 1 + 18);
+ DWIN_Draw_Rectangle(1, color, ICOX + 2, MBASE(line) - 1 + 2, ICOX + 17, MBASE(line) - 1 + 17);
+ onDrawMenuItem(menuitem, line);
+}
+
+void onDrawGetColorItem(MenuItemClass* menuitem, int8_t line) {
+ const uint8_t i = menuitem->icon;
+ uint16_t color;
+ switch (i) {
+ case 0: color = RGB(31, 0, 0); break; // Red
+ case 1: color = RGB(0, 63, 0); break; // Green
+ case 2: color = RGB(0, 0, 31); break; // Blue
+ default: color = 0; break;
+ }
+ DWIN_Draw_Rectangle(0, HMI_data.Highlight_Color, ICOX + 1, MBASE(line) - 1 + 1, ICOX + 18, MBASE(line) - 1 + 18);
+ DWIN_Draw_Rectangle(1, color, ICOX + 2, MBASE(line) - 1 + 2, ICOX + 17, MBASE(line) - 1 + 17);
+ DWINUI::Draw_String(LBLX, MBASE(line) - 1, menuitem->caption);
+ Draw_Menu_IntValue(HMI_data.Background_Color, line, 4, HMI_value.Color[i]);
+ DWIN_Draw_HLine(HMI_data.SplitLine_Color, 16, MYPOS(line + 1), 240);
+}
+
+#if HAS_FILAMENT_SENSOR
+ void onDrawRunoutEnable(MenuItemClass* menuitem, int8_t line) { onDrawChkbMenu(menuitem, line, runout.enabled); }
+#endif
+
+void onDrawPIDi(MenuItemClass* menuitem, int8_t line) { onDrawFloatMenu(menuitem, line, 2, unscalePID_i(*(float*)static_cast(menuitem)->value)); }
+void onDrawPIDd(MenuItemClass* menuitem, int8_t line) { onDrawFloatMenu(menuitem, line, 2, unscalePID_d(*(float*)static_cast(menuitem)->value)); }
+
+
+void onDrawSpeedItem(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 116, 164, 171, 176);
+ onDrawPIntMenu(menuitem, line);
+}
+
+#if HAS_HOTEND
+ void onDrawHotendTemp(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 1, 134, 56, 146);
+ onDrawPIntMenu(menuitem, line);
+ }
+#endif
+
+#if HAS_HEATED_BED
+ void onDrawBedTemp(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 58, 134, 113, 146);
+ onDrawPIntMenu(menuitem, line);
+ }
+#endif
+
+#if HAS_FAN
+ void onDrawFanSpeed(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 115, 134, 170, 146);
+ onDrawPInt8Menu(menuitem, line);
+ }
+#endif
+
+void onDrawSpeed(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 173, 133, 228, 147);
+ onDrawSubMenu(menuitem, line);
+}
+
+void onDrawAcc(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame(1, 173, 133, 200, 147);
+ DWIN_Frame_AreaCopy(1, 28, 149, 69, 161, LBLX + 27, MBASE(line) + 1); // ...Acceleration
+ }
+ onDrawSubMenu(menuitem, line);
+}
+
+#if HAS_CLASSIC_JERK
+ void onDrawJerk(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame(1, 173, 133, 200, 147);
+ DWIN_Frame_AreaCopy(1, 1, 180, 28, 192, LBLX + 27, MBASE(line) + 1); // ...
+ DWIN_Frame_AreaCopy(1, 202, 133, 228, 147, LBLX + 54, MBASE(line)); // ...Jerk
+ }
+ onDrawSubMenu(menuitem, line);
+ }
+#endif
+
+void onDrawSteps(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 153, 148, 194, 161);
+ onDrawSubMenu(menuitem, line);
+}
+
+#if ENABLED(MESH_BED_LEVELING)
+ void onDrawMMeshMoveZ(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 160, 118, 209, 132);
+ onDrawPFloatMenu(menuitem, line);
+ }
+#endif
+
+#if HAS_PREHEAT
+ #if HAS_HOTEND
+ void onDrawSetPreheatHotend(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 1, 134, 56, 146);
+ onDrawPIntMenu(menuitem, line);
+ }
+ #endif
+ #if HAS_HEATED_BED
+ void onDrawSetPreheatBed(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 58, 134, 113, 146);
+ onDrawPIntMenu(menuitem, line);
+ }
+ #endif
+ #if HAS_FAN
+ void onDrawSetPreheatFan(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 115, 134, 170, 146);
+ onDrawPIntMenu(menuitem, line);
+ }
+ #endif
+ void onDrawPLAPreheatSubMenu(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 100, 89, 178, 101);
+ onDrawSubMenu(menuitem,line);
+ }
+ void onDrawABSPreheatSubMenu(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) menuitem->SetFrame(1, 180, 89, 260, 100);
+ onDrawSubMenu(menuitem,line);
+ }
+#endif // HAS_HOTEND
+
+void onDrawMaxSpeedX(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame(1, 173, 133, 228, 147);
+ DWIN_Frame_AreaCopy(1, 229, 133, 236, 147, LBLX + 58, MBASE(line)); // X
+ }
+ onDrawPFloatMenu(menuitem, line);
+}
+
+void onDrawMaxSpeedY(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame(1, 173, 133, 228, 147);
+ DWIN_Frame_AreaCopy(1, 1, 150, 7, 160, LBLX + 58, MBASE(line)); // Y
+ }
+ onDrawPFloatMenu(menuitem, line);
+}
+
+void onDrawMaxSpeedZ(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame(1, 173, 133, 228, 147);
+ DWIN_Frame_AreaCopy(1, 9, 150, 16, 160, LBLX + 58, MBASE(line) + 3); // Z
+ }
+ onDrawPFloatMenu(menuitem, line);
+}
+
+#if HAS_HOTEND
+ void onDrawMaxSpeedE(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame(1, 173, 133, 228, 147);
+ DWIN_Frame_AreaCopy(1, 18, 150, 25, 160, LBLX + 58, MBASE(line)); // E
+ }
+ onDrawPFloatMenu(menuitem, line);
+ }
+#endif
+
+void onDrawMaxAccelX(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 173, 133, 200, 147);
+ DWIN_Frame_AreaCopy(1, 28, 149, 69, 161, LBLX + 27, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 229, 133, 236, 147, LBLX + 71, MBASE(line)); // X
+ }
+ onDrawPInt32Menu(menuitem, line);
+}
+
+void onDrawMaxAccelY(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 173, 133, 200, 147);
+ DWIN_Frame_AreaCopy(1, 28, 149, 69, 161, LBLX + 27, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 1, 150, 7, 160, LBLX + 71, MBASE(line)); // Y
+ }
+ onDrawPInt32Menu(menuitem, line);
+}
+
+void onDrawMaxAccelZ(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 173, 133, 200, 147);
+ DWIN_Frame_AreaCopy(1, 28, 149, 69, 161, LBLX + 27, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 9, 150, 16, 160, LBLX + 71, MBASE(line)); // Z
+ }
+ onDrawPInt32Menu(menuitem, line);
+}
+
+#if HAS_HOTEND
+ void onDrawMaxAccelE(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 173, 133, 200, 147);
+ DWIN_Frame_AreaCopy(1, 28, 149, 69, 161, LBLX + 27, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 18, 150, 25, 160, LBLX + 71, MBASE(line)); // E
+ }
+ onDrawPInt32Menu(menuitem, line);
+ }
+#endif
+
+#if HAS_CLASSIC_JERK
+ void onDrawMaxJerkX(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 173, 133, 200, 147);
+ DWIN_Frame_AreaCopy(1, 1, 180, 28, 192, LBLX + 27, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 202, 133, 228, 147, LBLX + 53, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 229, 133, 236, 147, LBLX + 83, MBASE(line));
+ }
+ onDrawPFloatMenu(menuitem, line);
+ }
+
+ void onDrawMaxJerkY(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 173, 133, 200, 147);
+ DWIN_Frame_AreaCopy(1, 1, 180, 28, 192, LBLX + 27, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 202, 133, 228, 147, LBLX + 53, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 1, 150, 7, 160, LBLX + 83, MBASE(line));
+ }
+ onDrawPFloatMenu(menuitem, line);
+ }
+
+ void onDrawMaxJerkZ(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 173, 133, 200, 147);
+ DWIN_Frame_AreaCopy(1, 1, 180, 28, 192, LBLX + 27, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 202, 133, 228, 147, LBLX + 53, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 9, 150, 16, 160, LBLX + 83, MBASE(line));
+ }
+ onDrawPFloatMenu(menuitem, line);
+ }
+
+ #if HAS_HOTEND
+ void onDrawMaxJerkE(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 173, 133, 200, 147);
+ DWIN_Frame_AreaCopy(1, 1, 180, 28, 192, LBLX + 27, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 202, 133, 228, 147, LBLX + 53, MBASE(line));
+ DWIN_Frame_AreaCopy(1, 18, 150, 25, 160, LBLX + 83, MBASE(line));
+ }
+ onDrawPFloatMenu(menuitem, line);
+ }
+ #endif
+#endif // HAS_CLASSIC_JERK
+
+void onDrawStepsX(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 153, 148, 194, 161);
+ DWIN_Frame_AreaCopy(1, 229, 133, 236, 147, LBLX + 44, MBASE(line)); // X
+ }
+ onDrawPFloatMenu(menuitem, line);
+}
+
+void onDrawStepsY(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 153, 148, 194, 161);
+ DWIN_Frame_AreaCopy(1, 1, 150, 7, 160, LBLX + 44, MBASE(line)); // Y
+ }
+ onDrawPFloatMenu(menuitem, line);
+}
+
+void onDrawStepsZ(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 153, 148, 194, 161);
+ DWIN_Frame_AreaCopy(1, 9, 150, 16, 160, LBLX + 44, MBASE(line)); // Z
+ }
+ onDrawPFloatMenu(menuitem, line);
+}
+
+#if HAS_HOTEND
+ void onDrawStepsE(MenuItemClass* menuitem, int8_t line) {
+ if (HMI_IsChinese()) {
+ menuitem->SetFrame (1, 153, 148, 194, 161);
+ DWIN_Frame_AreaCopy(1, 18, 150, 25, 160, LBLX + 44, MBASE(line)); // E
+ }
+ onDrawPFloatMenu(menuitem, line);
+ }
+#endif
+
+// HMI Control functions ======================================================
+
+// Generic menu control using the encoder
+void HMI_Menu() {
+ ENCODER_DiffState encoder_diffState = get_encoder_state();
+ if (encoder_diffState == ENCODER_DIFF_NO) return;
+ if (encoder_diffState == ENCODER_DIFF_ENTER) {
+ if (CurrentMenu != nullptr) CurrentMenu->onClick();
+ } else if (CurrentMenu != nullptr) CurrentMenu->onScroll(encoder_diffState == ENCODER_DIFF_CW);
+}
+
+// Get an integer value using the encoder without draw anything
+// lo: low limit
+// hi: high limit
+// Return value:
+// 0 : no change
+// 1 : live change
+// 2 : apply change
+int8_t HMI_GetIntNoDraw(const int32_t lo, const int32_t hi) {
+ ENCODER_DiffState encoder_diffState = Encoder_ReceiveAnalyze();
+ if (encoder_diffState != ENCODER_DIFF_NO) {
+ if (Apply_Encoder(encoder_diffState, HMI_value.Value)) {
+ EncoderRate.enabled = false;
+ checkkey = last_checkkey;
+ return 2;
+ }
+ LIMIT(HMI_value.Value, lo, hi);
+ return 1;
+ }
+ return 0;
+}
+
+// Get an integer value using the encoder
+// lo: low limit
+// hi: high limit
+// Return value:
+// 0 : no change
+// 1 : live change
+// 2 : apply change
+int8_t HMI_GetInt(const int32_t lo, const int32_t hi) {
+ ENCODER_DiffState encoder_diffState = Encoder_ReceiveAnalyze();
+ if (encoder_diffState != ENCODER_DIFF_NO) {
+ if (Apply_Encoder(encoder_diffState, HMI_value.Value)) {
+ EncoderRate.enabled = false;
+ DWINUI::Draw_Int(HMI_data.Text_Color, HMI_data.Background_Color, 4 , VALX, MBASE(CurrentMenu->line()) - 1, HMI_value.Value);
+ checkkey = last_checkkey;
+ return 2;
+ }
+ LIMIT(HMI_value.Value, lo, hi);
+ DWINUI::Draw_Int(HMI_data.Text_Color, HMI_data.Selected_Color, 4 , VALX, MBASE(CurrentMenu->line()) - 1, HMI_value.Value);
+ return 1;
+ }
+ return 0;
+}
+
+// Set an integer using the encoder
+void HMI_SetInt() {
+ int8_t val = HMI_GetInt(HMI_value.MinValue, HMI_value.MaxValue);
+ switch (val) {
+ case 0: return; break;
+ case 1: if (HMI_value.LiveUpdate != nullptr) HMI_value.LiveUpdate(); break;
+ case 2: if (HMI_value.Apply != nullptr) HMI_value.Apply(); break;
+ }
+}
+
+// Set an integer without drawing
+void HMI_SetIntNoDraw() {
+ int8_t val = HMI_GetIntNoDraw(HMI_value.MinValue, HMI_value.MaxValue);
+ switch (val) {
+ case 0: return; break;
+ case 1: if (HMI_value.LiveUpdate != nullptr) HMI_value.LiveUpdate(); break;
+ case 2: if (HMI_value.Apply != nullptr) HMI_value.Apply(); break;
+ }
+}
+
+// Set an integer pointer variable using the encoder
+void HMI_SetPInt() {
+ int8_t val = HMI_GetInt(HMI_value.MinValue, HMI_value.MaxValue);
+ if (!val) return;
+ else if (val == 2) { // Apply
+ *HMI_value.P_Int = HMI_value.Value;
+ if (HMI_value.Apply != nullptr) HMI_value.Apply();
+ } else if (HMI_value.LiveUpdate != nullptr) HMI_value.LiveUpdate();
+}
+
+// Get an scaled float value using the encoder
+// dp: decimal places
+// lo: scaled low limit
+// hi: scaled high limit
+// Return value:
+// 0 : no change
+// 1 : live change
+// 2 : apply change
+int8_t HMI_GetFloat(uint8_t dp, int32_t lo, int32_t hi) {
+ ENCODER_DiffState encoder_diffState = Encoder_ReceiveAnalyze();
+ if (encoder_diffState != ENCODER_DIFF_NO) {
+ if (Apply_Encoder(encoder_diffState, HMI_value.Value)) {
+ EncoderRate.enabled = false;
+ DWINUI::Draw_Signed_Float(HMI_data.Text_Color, HMI_data.Background_Color, 3, dp, VALX - dp * DWINUI::Get_font_width(DWIN_FONT_MENU), MBASE(CurrentMenu->line()), HMI_value.Value / POW(10, dp));
+ checkkey = last_checkkey;
+ return 2;
+ }
+ LIMIT(HMI_value.Value, lo, hi);
+ DWINUI::Draw_Signed_Float(HMI_data.Text_Color, HMI_data.Selected_Color, 3, dp, VALX - dp * DWINUI::Get_font_width(DWIN_FONT_MENU), MBASE(CurrentMenu->line()), HMI_value.Value / POW(10, dp));
+ return 1;
+ }
+ return 0;
+}
+
+// Set an scaled float using the encoder
+void HMI_SetFloat() {
+ int8_t val = HMI_GetFloat(HMI_value.dp, HMI_value.MinValue, HMI_value.MaxValue);
+ switch (val) {
+ case 0: return; break;
+ case 1: if (HMI_value.LiveUpdate != nullptr) HMI_value.LiveUpdate(); break;
+ case 2: if (HMI_value.Apply != nullptr) HMI_value.Apply(); break;
+ }
+}
+
+// Set an scaled float pointer variable using the encoder
+void HMI_SetPFloat() {
+ int8_t val = HMI_GetFloat(HMI_value.dp, HMI_value.MinValue, HMI_value.MaxValue);
+ if (!val) return;
+ else if (val == 2) { // Apply
+ *HMI_value.P_Float = HMI_value.Value / POW(10, HMI_value.dp);
+ if (HMI_value.Apply != nullptr) HMI_value.Apply();
+ } else if (HMI_value.LiveUpdate != nullptr) HMI_value.LiveUpdate();
+}
+
+// Menu Creation and Drawing functions ======================================================
+
+void SetMenuTitle(frame_rect_t cn, frame_rect_t en, const __FlashStringHelper* text) {
+ if (HMI_IsChinese() && (cn.w != 0))
+ CurrentMenu->MenuTitle.SetFrame(cn.x, cn.y, cn.w, cn.h);
+ else {
+ #ifdef USE_STRING_HEADINGS
+ CurrentMenu->MenuTitle.SetCaption(text);
+ #else
+ if (en.w != 0) CurrentMenu->MenuTitle.SetFrame(en.x, en.y, en.w, en.h);
+ #endif
+ }
+}
+
+void Draw_Prepare_Menu() {
+ checkkey = Menu;
+ if (PrepareMenu == nullptr) PrepareMenu = new MenuClass();
+ if (CurrentMenu != PrepareMenu) {
+ CurrentMenu = PrepareMenu;
+ SetMenuTitle({133, 1, 28, 13}, {179, 0, 48, 14}, GET_TEXT_F(MSG_PREPARE));
+ DWINUI::MenuItemsPrepare(13);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Goto_Main_Menu);
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ ADDMENUITEM(ICON_FilMan, GET_TEXT(MSG_FILAMENT_MAN), onDrawSubMenu, Draw_FilamentMan_Menu);
+ #endif
+ ADDMENUITEM(ICON_Axis, GET_TEXT(MSG_MOVE_AXIS), onDrawMoveSubMenu, Draw_Move_Menu);
+ ADDMENUITEM(ICON_LevBed, GET_TEXT(MSG_BED_LEVELING), onDrawSubMenu, Draw_LevBedCorners_Menu);
+ ADDMENUITEM(ICON_CloseMotor, GET_TEXT(MSG_DISABLE_STEPPERS), onDrawDisableMotors, DisableMotors);
+ ADDMENUITEM(ICON_Homing, GET_TEXT(MSG_AUTO_HOME), onDrawAutoHome, AutoHome);
+ #if ENABLED(MESH_BED_LEVELING)
+ ADDMENUITEM(ICON_ManualMesh, GET_TEXT(MSG_MANUAL_MESH), onDrawSubMenu, Draw_ManualMesh_Menu);
+ #endif
+ #if HAS_ZOFFSET_ITEM
+ #if EITHER(HAS_BED_PROBE, BABYSTEPPING)
+ ADDMENUITEM(ICON_SetZOffset, GET_TEXT(MSG_PROBE_WIZARD), onDrawSubMenu, Draw_ZOffsetWiz_Menu);
+ #else
+ ADDMENUITEM(ICON_SetHome, GET_TEXT(MSG_SET_HOME_OFFSETS), onDrawHomeOffset, SetHome);
+ #endif
+ #endif
+ #if HAS_HOTEND
+ ADDMENUITEM(ICON_PLAPreheat, GET_TEXT(MSG_PREHEAT_1), onDrawPreheat1, SetPreheat0);
+ ADDMENUITEM(ICON_ABSPreheat, PSTR("Preheat " PREHEAT_2_LABEL), onDrawPreheat2, SetPreheat1);
+ ADDMENUITEM(ICON_CustomPreheat, GET_TEXT(MSG_PREHEAT_CUSTOM), onDrawMenuItem, SetPreheat2);
+ #endif
+ #if HAS_PREHEAT
+ ADDMENUITEM(ICON_Cool, GET_TEXT(MSG_COOLDOWN), onDrawCooldown, SetCoolDown);
+ #endif
+ ADDMENUITEM(ICON_Language, PSTR("UI Language"), onDrawLanguage, SetLanguage);
+ }
+ CurrentMenu->Draw();
+}
+
+void Draw_LevBedCorners_Menu() {
+ DWINUI::ClearMenuArea();
+ checkkey = Menu;
+ if (LevBedMenu == nullptr) LevBedMenu = new MenuClass();
+ if (CurrentMenu != LevBedMenu) {
+ CurrentMenu = LevBedMenu;
+ SetMenuTitle({0}, {0}, GET_TEXT_F(MSG_BED_TRAMMING)); // TODO: Chinese, English "Bed Tramming" JPG
+ DWINUI::MenuItemsPrepare(6);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Prepare_Menu);
+ ADDMENUITEM(ICON_Axis, GET_TEXT(MSG_LEVBED_FL), onDrawMenuItem, LevBedFL);
+ ADDMENUITEM(ICON_Axis, GET_TEXT(MSG_LEVBED_FR), onDrawMenuItem, LevBedFR);
+ ADDMENUITEM(ICON_Axis, GET_TEXT(MSG_LEVBED_BR), onDrawMenuItem, LevBedBR);
+ ADDMENUITEM(ICON_Axis, GET_TEXT(MSG_LEVBED_BL), onDrawMenuItem, LevBedBL);
+ ADDMENUITEM(ICON_Axis, GET_TEXT(MSG_LEVBED_C ), onDrawMenuItem, LevBedC );
+ }
+ CurrentMenu->Draw();
+}
+
+void Draw_Control_Menu() {
+ checkkey = Menu;
+ if (ControlMenu == nullptr) ControlMenu = new MenuClass();
+ if (CurrentMenu != ControlMenu) {
+ CurrentMenu = ControlMenu;
+ SetMenuTitle({103, 1, 28, 14}, {128, 2, 49, 11}, GET_TEXT_F(MSG_CONTROL));
+ DWINUI::MenuItemsPrepare(9);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Goto_Main_Menu);
+ ADDMENUITEM(ICON_Temperature, GET_TEXT(MSG_TEMPERATURE), onDrawTempSubMenu, Draw_Temperature_Menu);
+ ADDMENUITEM(ICON_Motion, GET_TEXT(MSG_MOTION), onDrawMotionSubMenu, Draw_Motion_Menu);
+ #if ENABLED(EEPROM_SETTINGS)
+ ADDMENUITEM(ICON_WriteEEPROM, GET_TEXT(MSG_STORE_EEPROM), onDrawWriteEeprom, WriteEeprom);
+ ADDMENUITEM(ICON_ReadEEPROM, GET_TEXT(MSG_LOAD_EEPROM), onDrawReadEeprom, ReadEeprom);
+ ADDMENUITEM(ICON_ResumeEEPROM, GET_TEXT(MSG_RESTORE_DEFAULTS), onDrawResetEeprom, ResetEeprom);
+ #endif
+ ADDMENUITEM(ICON_Reboot, GET_TEXT(MSG_RESET_PRINTER), onDrawMenuItem, RebootPrinter);
+ ADDMENUITEM(ICON_AdvSet, GET_TEXT(MSG_ADVANCED_SETTINGS), onDrawSubMenu, Draw_AdvancedSettings_Menu);
+ ADDMENUITEM(ICON_Info, GET_TEXT(MSG_INFO_SCREEN), onDrawInfoSubMenu, Goto_InfoMenu);
+ }
+ CurrentMenu->Draw();
+}
+
+void Draw_AdvancedSettings_Menu() {
+ checkkey = Menu;
+ if (AdvancedSettings == nullptr) AdvancedSettings = new MenuClass();
+ if (CurrentMenu != AdvancedSettings) {
+ CurrentMenu = AdvancedSettings;
+ SetMenuTitle({0}, {0}, GET_TEXT_F(MSG_ADVANCED_SETTINGS)); // TODO: Chinese, English "Advanced Settings" JPG
+ DWINUI::MenuItemsPrepare(11);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Control_Menu);
+ #if HAS_HOME_OFFSET
+ ADDMENUITEM(ICON_HomeOffset, GET_TEXT(MSG_SET_HOME_OFFSETS), onDrawSubMenu, Draw_HomeOffset_Menu);
+ #endif
+ #if HAS_BED_PROBE
+ ADDMENUITEM(ICON_ProbeSet, GET_TEXT(MSG_ZPROBE_SETTINGS), onDrawSubMenu, Draw_ProbeSet_Menu);
+ #endif
+ #if HAS_HOTEND
+ ADDMENUITEM(ICON_PIDNozzle, F("Hotend PID Settings"), onDrawSubMenu, Draw_HotendPID_Menu);
+ #endif
+ #if HAS_HEATED_BED
+ ADDMENUITEM(ICON_PIDbed, F("Bed PID Settings"), onDrawSubMenu, Draw_BedPID_Menu);
+ #endif
+ #if HAS_FILAMENT_SENSOR
+ ADDMENUITEM(ICON_FilSet, GET_TEXT(MSG_FILAMENT_SET), onDrawSubMenu, Draw_FilSet_Menu);
+ #endif
+ #if ENABLED(POWER_LOSS_RECOVERY)
+ ADDMENUITEM(ICON_Pwrlossr, F("Power-loss recovery"), onDrawPwrLossR, SetPwrLossr);
+ #endif
+ #if HAS_LCD_BRIGHTNESS
+ ADDMENUITEM_P(ICON_Brightness, F("LCD Brightness"), onDrawPInt8Menu, SetBrightness, &ui.brightness);
+ #endif
+ ADDMENUITEM(ICON_Scolor, F("Select Colors"), onDrawSubMenu, Draw_SelectColors_Menu);
+ #if ENABLED(SOUND_MENU_ITEM)
+ ADDMENUITEM(ICON_Sound, F("Enable Sound"), onDrawEnableSound, SetEnableSound);
+ #endif
+ ADDMENUITEM(ICON_Lock, F("Lock Screen"), onDrawMenuItem, Goto_LockScreen);
+ }
+ CurrentMenu->Draw();
+}
+
+void Draw_Move_Menu() {
+ checkkey = Menu;
+ if (MoveMenu == nullptr) MoveMenu = new MenuClass();
+ if (CurrentMenu != MoveMenu) {
+ CurrentMenu = MoveMenu;
+ SetMenuTitle({192, 1, 42, 14}, {231, 2, 35, 11}, GET_TEXT_F(MSG_MOVE_AXIS));
+ DWINUI::MenuItemsPrepare(5);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Prepare_Menu);
+ ADDMENUITEM_P(ICON_MoveX, GET_TEXT(MSG_MOVE_X), onDrawMoveX, SetMoveX, ¤t_position.x);
+ ADDMENUITEM_P(ICON_MoveY, GET_TEXT(MSG_MOVE_Y), onDrawMoveY, SetMoveY, ¤t_position.y);
+ ADDMENUITEM_P(ICON_MoveZ, GET_TEXT(MSG_MOVE_Z), onDrawMoveZ, SetMoveZ, ¤t_position.z);
+ #if HAS_HOTEND
+ ADDMENUITEM_P(ICON_Extruder, GET_TEXT(MSG_MOVE_E), onDrawMoveE, SetMoveE, ¤t_position.e);
+ #endif
+ }
+ CurrentMenu->Draw();
+ if (!all_axes_trusted()) ui.set_status_P(PSTR("WARNING: position is unknow"));
+}
+
+#if HAS_HOME_OFFSET
+ void Draw_HomeOffset_Menu() {
+ checkkey = Menu;
+ if (HomeOffMenu == nullptr) HomeOffMenu = new MenuClass();
+ if (CurrentMenu != HomeOffMenu) {
+ CurrentMenu = HomeOffMenu;
+ SetMenuTitle({0}, {0}, GET_TEXT_F(MSG_SET_HOME_OFFSETS)); // TODO: Chinese, English "Set Home Offsets" JPG
+ DWINUI::MenuItemsPrepare(4);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_AdvancedSettings_Menu);
+ ADDMENUITEM_P(ICON_HomeOffsetX, GET_TEXT(MSG_HOME_OFFSET_X), onDrawPFloatMenu, SetHomeOffsetX, &home_offset[X_AXIS]);
+ ADDMENUITEM_P(ICON_HomeOffsetY, GET_TEXT(MSG_HOME_OFFSET_Y), onDrawPFloatMenu, SetHomeOffsetY, &home_offset[Y_AXIS]);
+ ADDMENUITEM_P(ICON_HomeOffsetZ, GET_TEXT(MSG_HOME_OFFSET_Z), onDrawPFloatMenu, SetHomeOffsetZ, &home_offset[Z_AXIS]);
+ }
+ CurrentMenu->Draw();
+ }
+#endif
+
+#if HAS_BED_PROBE
+ void Draw_ProbeSet_Menu() {
+ checkkey = Menu;
+ if (ProbeSetMenu == nullptr) ProbeSetMenu = new MenuClass();
+ if (CurrentMenu != ProbeSetMenu) {
+ CurrentMenu = ProbeSetMenu;
+ SetMenuTitle({0}, {0}, GET_TEXT_F(MSG_ZPROBE_SETTINGS)); // TODO: Chinese, English "Probe Settings" JPG
+ DWINUI::MenuItemsPrepare(5);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_AdvancedSettings_Menu);
+ ADDMENUITEM_P(ICON_ProbeOffsetX, GET_TEXT(MSG_ZPROBE_XOFFSET), onDrawPFloatMenu, SetProbeOffsetX, &probe.offset.x);
+ ADDMENUITEM_P(ICON_ProbeOffsetY, GET_TEXT(MSG_ZPROBE_YOFFSET), onDrawPFloatMenu, SetProbeOffsetY, &probe.offset.y);
+ ADDMENUITEM_P(ICON_ProbeOffsetZ, GET_TEXT(MSG_ZPROBE_ZOFFSET), onDrawPFloat2Menu, SetProbeOffsetZ, &probe.offset.z);
+ ADDMENUITEM(ICON_ProbeTest, GET_TEXT(MSG_M48_TEST), onDrawMenuItem, ProbeTest);
+ }
+ CurrentMenu->Draw();
+ }
+#endif
+
+#if HAS_FILAMENT_SENSOR
+ void Draw_FilSet_Menu() {
+ checkkey = Menu;
+ if (FilSetMenu == nullptr) FilSetMenu = new MenuClass();
+ if (CurrentMenu != FilSetMenu) {
+ CurrentMenu = FilSetMenu;
+ CurrentMenu->MenuTitle.SetCaption(GET_TEXT_F(MSG_FILAMENT_SET));
+ DWINUI::MenuItemsPrepare(6);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawMenuItem, Draw_AdvancedSettings_Menu);
+ #if HAS_FILAMENT_SENSOR
+ ADDMENUITEM(ICON_Runout, GET_TEXT(MSG_RUNOUT_ENABLE), onDrawRunoutEnable, SetRunoutEnable);
+ #endif
+ #if HAS_FILAMENT_RUNOUT_DISTANCE
+ ADDMENUITEM_P(ICON_Runout, F("Runout Distance"), onDrawPFloatMenu, SetRunoutDistance, &runout.runout_distance());
+ #endif
+ #if ENABLED(PREVENT_COLD_EXTRUSION)
+ ADDMENUITEM_P(ICON_ExtrudeMinT, F("Extrude Min Temp."), onDrawPIntMenu, SetExtMinT, &HMI_data.ExtMinT);
+ #endif
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ ADDMENUITEM_P(ICON_FilLoad, GET_TEXT(MSG_FILAMENT_LOAD), onDrawPFloatMenu, SetFilLoad, &fc_settings[0].load_length);
+ ADDMENUITEM_P(ICON_FilUnload, GET_TEXT(MSG_FILAMENT_UNLOAD), onDrawPFloatMenu, SetFilUnload, &fc_settings[0].unload_length);
+ #endif
+ }
+ CurrentMenu->Draw();
+ }
+#endif
+void Draw_SelectColors_Menu() {
+ checkkey = Menu;
+ if (SelectColorMenu == nullptr) SelectColorMenu = new MenuClass();
+ if (CurrentMenu != SelectColorMenu) {
+ CurrentMenu = SelectColorMenu;
+ SetMenuTitle({0}, {0}, F("Select Colors")); // TODO: Chinese, English "Select Color" JPG
+ DWINUI::MenuItemsPrepare(20);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_AdvancedSettings_Menu);
+ ADDMENUITEM(ICON_StockConfiguration, GET_TEXT(MSG_RESTORE_DEFAULTS), onDrawMenuItem, RestoreDefaultsColors);
+ ADDMENUITEM_P(0, "Screen Background", onDrawSelColorItem, SelColor, &HMI_data.Background_Color);
+ ADDMENUITEM_P(0, "Cursor", onDrawSelColorItem, SelColor, &HMI_data.Cursor_color);
+ ADDMENUITEM_P(0, "Title Background", onDrawSelColorItem, SelColor, &HMI_data.TitleBg_color);
+ ADDMENUITEM_P(0, "Title Text", onDrawSelColorItem, SelColor, &HMI_data.TitleTxt_color);
+ ADDMENUITEM_P(0, "Text", onDrawSelColorItem, SelColor, &HMI_data.Text_Color);
+ ADDMENUITEM_P(0, "Selected", onDrawSelColorItem, SelColor, &HMI_data.Selected_Color);
+ ADDMENUITEM_P(0, "Split Line", onDrawSelColorItem, SelColor, &HMI_data.SplitLine_Color);
+ ADDMENUITEM_P(0, "Highlight", onDrawSelColorItem, SelColor, &HMI_data.Highlight_Color);
+ ADDMENUITEM_P(0, "Status Background", onDrawSelColorItem, SelColor, &HMI_data.StatusBg_Color);
+ ADDMENUITEM_P(0, "Status Text", onDrawSelColorItem, SelColor, &HMI_data.StatusTxt_Color);
+ ADDMENUITEM_P(0, "Popup Background", onDrawSelColorItem, SelColor, &HMI_data.PopupBg_color);
+ ADDMENUITEM_P(0, "Popup Text", onDrawSelColorItem, SelColor, &HMI_data.PopupTxt_Color);
+ ADDMENUITEM_P(0, "Alert Background", onDrawSelColorItem, SelColor, &HMI_data.AlertBg_Color);
+ ADDMENUITEM_P(0, "Alert Text", onDrawSelColorItem, SelColor, &HMI_data.AlertTxt_Color);
+ ADDMENUITEM_P(0, "Percent Text", onDrawSelColorItem, SelColor, &HMI_data.PercentTxt_Color);
+ ADDMENUITEM_P(0, "Bar Fill", onDrawSelColorItem, SelColor, &HMI_data.Barfill_Color);
+ ADDMENUITEM_P(0, "Indicator value", onDrawSelColorItem, SelColor, &HMI_data.Indicator_Color);
+ ADDMENUITEM_P(0, "Coordinate value", onDrawSelColorItem, SelColor, &HMI_data.Coordinate_Color);
+ }
+ CurrentMenu->Draw();
+}
+
+void Draw_GetColor_Menu() {
+ checkkey = Menu;
+ if (GetColorMenu == nullptr) GetColorMenu = new MenuClass();
+ if (CurrentMenu != GetColorMenu) {
+ CurrentMenu = GetColorMenu;
+ SetMenuTitle({0}, {0}, F("Get Color")); // TODO: Chinese, English "Get Color" JPG
+ DWINUI::MenuItemsPrepare(5);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, DWIN_ApplyColor);
+ ADDMENUITEM(ICON_Cancel, GET_TEXT(MSG_BUTTON_CANCEL), onDrawMenuItem, Draw_SelectColors_Menu);
+ ADDMENUITEM(0, "Red", onDrawGetColorItem, SetRGBColor);
+ ADDMENUITEM(1, "Green", onDrawGetColorItem, SetRGBColor);
+ ADDMENUITEM(2, "Blue", onDrawGetColorItem, SetRGBColor);
+ }
+ CurrentMenu->Draw();
+ DWIN_Draw_Rectangle(1, *HMI_value.P_Int, 20, 315, DWIN_WIDTH - 20, 335);
+}
+
+void Draw_Tune_Menu() {
+ checkkey = Menu;
+ if (TuneMenu == nullptr) TuneMenu = new MenuClass();
+ if (CurrentMenu != TuneMenu) {
+ CurrentMenu = TuneMenu;
+ SetMenuTitle({73, 2, 28, 12}, {94, 2, 33, 11}, GET_TEXT_F(MSG_TUNE)); // TODO: Chinese, English "Tune" JPG
+ DWINUI::MenuItemsPrepare(10);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Goto_PrintProcess);
+ ADDMENUITEM_P(ICON_Speed, GET_TEXT(MSG_SPEED), onDrawSpeedItem, SetSpeed, &feedrate_percentage);
+ #if HAS_HOTEND
+ HotendTargetItem = ADDMENUITEM_P(ICON_HotendTemp, GET_TEXT(MSG_UBL_SET_TEMP_HOTEND), onDrawHotendTemp, SetHotendTemp, &thermalManager.temp_hotend[0].target);
+ #endif
+ #if HAS_HEATED_BED
+ BedTargetItem = ADDMENUITEM_P(ICON_BedTemp, GET_TEXT(MSG_UBL_SET_TEMP_BED), onDrawBedTemp, SetBedTemp, &thermalManager.temp_bed.target);
+ #endif
+ #if HAS_FAN
+ FanSpeedItem = ADDMENUITEM_P(ICON_FanSpeed, GET_TEXT(MSG_FAN_SPEED), onDrawFanSpeed, SetFanSpeed, &thermalManager.fan_speed[0]);
+ #endif
+ #if HAS_ZOFFSET_ITEM && EITHER(HAS_BED_PROBE, BABYSTEPPING)
+ ADDMENUITEM_P(ICON_Zoffset, GET_TEXT(MSG_ZPROBE_ZOFFSET), onDrawZOffset, SetZOffset, &BABY_Z_VAR);
+ #endif
+ ADDMENUITEM_P(ICON_Flow, GET_TEXT(MSG_FLOW), onDrawPIntMenu, SetFlow, &planner.flow_percentage[0]);
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ ADDMENUITEM(ICON_FilMan, GET_TEXT(MSG_FILAMENTCHANGE), onDrawMenuItem, ChangeFilament);
+ #endif
+ ADDMENUITEM(ICON_Lock, F("Lock Screen"), onDrawMenuItem, Goto_LockScreen);
+ #if HAS_LCD_BRIGHTNESS
+ ADDMENUITEM_P(ICON_Brightness, F("LCD Brightness"), onDrawPInt8Menu, SetBrightness, &ui.brightness);
+ #endif
+ }
+ CurrentMenu->Draw();
+}
+
+void Draw_Motion_Menu() {
+ checkkey = Menu;
+ if (MotionMenu == nullptr) MotionMenu = new MenuClass();
+ if (CurrentMenu != MotionMenu) {
+ CurrentMenu = MotionMenu;
+ SetMenuTitle({1, 16, 28, 13}, {144, 16, 46, 11}, GET_TEXT_F(MSG_MOTION)); // TODO: Chinese, English "Motion" JPG
+ DWINUI::MenuItemsPrepare(6);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Control_Menu);
+ ADDMENUITEM(ICON_MaxSpeed, GET_TEXT(MSG_SPEED), onDrawSpeed, Draw_MaxSpeed_Menu);
+ ADDMENUITEM(ICON_MaxAccelerated, GET_TEXT(MSG_ACCELERATION), onDrawAcc, Draw_MaxAccel_Menu);
+ #if HAS_CLASSIC_JERK
+ ADDMENUITEM(ICON_MaxJerk, GET_TEXT(MSG_JERK), onDrawJerk, Draw_MaxJerk_Menu);
+ #endif
+ ADDMENUITEM(ICON_Step, GET_TEXT(MSG_STEPS_PER_MM), onDrawSteps, Draw_Steps_Menu);
+ ADDMENUITEM_P(ICON_Flow, GET_TEXT(MSG_FLOW), onDrawPIntMenu, SetFlow, &planner.flow_percentage[0]);
+ }
+ CurrentMenu->Draw();
+}
+
+#if ENABLED(ADVANCED_PAUSE_FEATURE)
+ void Draw_FilamentMan_Menu() {
+ checkkey = Menu;
+ if (FilamentMenu == nullptr) FilamentMenu = new MenuClass();
+ if (CurrentMenu != FilamentMenu) {
+ CurrentMenu = FilamentMenu;
+ SetMenuTitle({0}, {0}, GET_TEXT_F(MSG_FILAMENT_MAN)); // TODO: Chinese, English "Filament Management" JPG
+ DWINUI::MenuItemsPrepare(5);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Prepare_Menu);
+ ADDMENUITEM(ICON_Park, GET_TEXT(MSG_FILAMENT_PARK_ENABLED), onDrawMenuItem, ParkHead);
+ ADDMENUITEM(ICON_FilMan, GET_TEXT(MSG_FILAMENTCHANGE), onDrawMenuItem, ChangeFilament);
+ #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ ADDMENUITEM(ICON_FilUnload, GET_TEXT(MSG_FILAMENTUNLOAD), onDrawMenuItem, UnloadFilament);
+ ADDMENUITEM(ICON_FilLoad, GET_TEXT(MSG_FILAMENTLOAD), onDrawMenuItem, LoadFilament);
+ #endif
+ }
+ CurrentMenu->Draw();
+ }
+#endif
+
+#if ENABLED(MESH_BED_LEVELING)
+ void Draw_ManualMesh_Menu() {
+ checkkey = Menu;
+ if (ManualMesh == nullptr) ManualMesh = new MenuClass();
+ if (CurrentMenu != ManualMesh) {
+ CurrentMenu = ManualMesh;
+ SetMenuTitle({0}, {0}, GET_TEXT_F(MSG_MANUAL_MESH)); // TODO: Chinese, English "Manual Mesh Leveling" JPG
+ DWINUI::MenuItemsPrepare(5);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Prepare_Menu);
+ ADDMENUITEM(ICON_ManualMesh, GET_TEXT(MSG_LEVEL_BED), onDrawMenuItem, ManualMeshStart);
+ MMeshMoveZItem = ADDMENUITEM_P(ICON_Zoffset, GET_TEXT(MSG_MOVE_Z), onDrawMMeshMoveZ, SetMMeshMoveZ, ¤t_position.z);
+ ADDMENUITEM(ICON_Axis, GET_TEXT(MSG_UBL_CONTINUE_MESH), onDrawMenuItem, ManualMeshContinue);
+ ADDMENUITEM(ICON_MeshSave, GET_TEXT(MSG_UBL_SAVE_MESH), onDrawMenuItem, ManualMeshSave);
+ }
+ CurrentMenu->Draw();
+ }
+#endif
+
+#if HAS_PREHEAT
+ void Draw_Preheat_Menu(frame_rect_t cn, frame_rect_t en, const __FlashStringHelper* text) {
+ checkkey = Menu;
+ if (CurrentMenu != PreheatMenu) {
+ CurrentMenu = PreheatMenu;
+ SetMenuTitle(cn, en, text);
+ DWINUI::MenuItemsPrepare(5);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Temperature_Menu);
+ #if HAS_HOTEND
+ ADDMENUITEM_P(ICON_SetEndTemp, GET_TEXT(MSG_UBL_SET_TEMP_HOTEND), onDrawSetPreheatHotend, SetPreheatEndTemp, &ui.material_preset[HMI_value.Preheat].hotend_temp);
+ #endif
+ #if HAS_HEATED_BED
+ ADDMENUITEM_P(ICON_SetBedTemp, GET_TEXT(MSG_UBL_SET_TEMP_BED), onDrawSetPreheatBed, SetPreheatBedTemp, &ui.material_preset[HMI_value.Preheat].bed_temp);
+ #endif
+ #if HAS_FAN
+ ADDMENUITEM_P(ICON_FanSpeed, GET_TEXT(MSG_FAN_SPEED), onDrawSetPreheatFan, SetPreheatFanSpeed, &ui.material_preset[HMI_value.Preheat].fan_speed);
+ #endif
+ #if ENABLED(EEPROM_SETTINGS)
+ ADDMENUITEM(ICON_WriteEEPROM, GET_TEXT(MSG_STORE_EEPROM), onDrawWriteEeprom, WriteEeprom);
+ #endif
+ }
+ CurrentMenu->Draw();
+ }
+
+ void Draw_Preheat1_Menu() {
+ HMI_value.Preheat = 0;
+ if (PreheatMenu == nullptr) PreheatMenu = new MenuClass();
+ Draw_Preheat_Menu({59, 16, 81, 14}, {56, 15, 85, 14}, F(PREHEAT_1_LABEL " Preheat Settings")); // TODO: English "PLA Settings" JPG
+ }
+
+ void Draw_Preheat2_Menu() {
+ HMI_value.Preheat = 1;
+ if (PreheatMenu == nullptr) PreheatMenu = new MenuClass();
+ Draw_Preheat_Menu({142, 16, 82, 14}, {56, 15, 85, 14}, F(PREHEAT_2_LABEL " Preheat Settings")); // TODO: English "ABS Settings" JPG
+ }
+
+ #ifdef PREHEAT_3_LABEL
+ void Draw_Preheat3_Menu() {
+ HMI_value.Preheat = 2;
+ if (PreheatMenu == nullptr) PreheatMenu = new MenuClass();
+ #define PREHEAT_3_TITLE PREHEAT_3_LABEL " Preheat Set."
+ Draw_Preheat_Menu({0}, {0}, F(PREHEAT_3_TITLE)); // TODO: Chinese, English "Custom Preheat Settings" JPG
+ }
+ #endif
+
+#endif
+
+void Draw_Temperature_Menu() {
+ checkkey = Menu;
+ if (TemperatureMenu == nullptr) TemperatureMenu = new MenuClass();
+ if (CurrentMenu != TemperatureMenu) {
+ CurrentMenu = TemperatureMenu;
+ SetMenuTitle({236, 2, 28, 12}, {56, 15, 85, 14}, GET_TEXT_F(MSG_TEMPERATURE));
+ DWINUI::MenuItemsPrepare(7);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Control_Menu);
+ #if HAS_HOTEND
+ HotendTargetItem = ADDMENUITEM_P(ICON_SetEndTemp, GET_TEXT(MSG_UBL_SET_TEMP_HOTEND), onDrawHotendTemp, SetHotendTemp, &thermalManager.temp_hotend[0].target);
+ #endif
+ #if HAS_HEATED_BED
+ BedTargetItem = ADDMENUITEM_P(ICON_SetBedTemp, GET_TEXT(MSG_UBL_SET_TEMP_BED), onDrawBedTemp, SetBedTemp, &thermalManager.temp_bed.target);
+ #endif
+ #if HAS_FAN
+ FanSpeedItem = ADDMENUITEM_P(ICON_FanSpeed, GET_TEXT(MSG_FAN_SPEED), onDrawFanSpeed, SetFanSpeed, &thermalManager.fan_speed[0]);
+ #endif
+ #if HAS_HOTEND
+ ADDMENUITEM(ICON_SetPLAPreheat, F(PREHEAT_1_LABEL " Preheat Settings"), onDrawPLAPreheatSubMenu, Draw_Preheat1_Menu);
+ ADDMENUITEM(ICON_SetABSPreheat, F(PREHEAT_2_LABEL " Preheat Settings"), onDrawABSPreheatSubMenu, Draw_Preheat2_Menu);
+ #ifdef PREHEAT_3_LABEL
+ ADDMENUITEM(ICON_SetCustomPreheat, PREHEAT_3_TITLE, onDrawSubMenu, Draw_Preheat3_Menu);
+ #endif
+ #endif
+ }
+ CurrentMenu->Draw();
+}
+
+void Draw_MaxSpeed_Menu() {
+ checkkey = Menu;
+ if (MaxSpeedMenu == nullptr) MaxSpeedMenu = new MenuClass();
+ if (CurrentMenu != MaxSpeedMenu) {
+ CurrentMenu = MaxSpeedMenu;
+ SetMenuTitle({1, 16, 28, 13}, {144, 16, 46, 11}, GET_TEXT_F(MSG_MAXSPEED));
+ DWINUI::MenuItemsPrepare(5);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Motion_Menu);
+ ADDMENUITEM_P(ICON_MaxSpeedX, GET_TEXT(MSG_MAXSPEED_X), onDrawMaxSpeedX, SetMaxSpeedX, &planner.settings.max_feedrate_mm_s[X_AXIS]);
+ ADDMENUITEM_P(ICON_MaxSpeedY, GET_TEXT(MSG_MAXSPEED_Y), onDrawMaxSpeedY, SetMaxSpeedY, &planner.settings.max_feedrate_mm_s[Y_AXIS]);
+ ADDMENUITEM_P(ICON_MaxSpeedZ, GET_TEXT(MSG_MAXSPEED_Z), onDrawMaxSpeedZ, SetMaxSpeedZ, &planner.settings.max_feedrate_mm_s[Z_AXIS]);
+ #if HAS_HOTEND
+ ADDMENUITEM_P(ICON_MaxSpeedE, GET_TEXT(MSG_MAXSPEED_E), onDrawMaxSpeedE, SetMaxSpeedE, &planner.settings.max_feedrate_mm_s[Z_AXIS]);
+ #endif
+ }
+ CurrentMenu->Draw();
+}
+
+void Draw_MaxAccel_Menu() {
+ checkkey = Menu;
+ if (MaxAccelMenu == nullptr) MaxAccelMenu = new MenuClass();
+ if (CurrentMenu != MaxAccelMenu) {
+ CurrentMenu = MaxAccelMenu;
+ SetMenuTitle({1, 16, 28, 13}, {144, 16, 46, 11}, GET_TEXT_F(MSG_ACCELERATION));
+ DWINUI::MenuItemsPrepare(5);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Motion_Menu);
+ ADDMENUITEM_P(ICON_MaxAccX, GET_TEXT(MSG_AMAX_A), onDrawMaxAccelX, SetMaxAccelX, &planner.settings.max_acceleration_mm_per_s2[X_AXIS]);
+ ADDMENUITEM_P(ICON_MaxAccY, GET_TEXT(MSG_AMAX_B), onDrawMaxAccelY, SetMaxAccelY, &planner.settings.max_acceleration_mm_per_s2[Y_AXIS]);
+ ADDMENUITEM_P(ICON_MaxAccZ, GET_TEXT(MSG_AMAX_C), onDrawMaxAccelZ, SetMaxAccelZ, &planner.settings.max_acceleration_mm_per_s2[Z_AXIS]);
+ #if HAS_HOTEND
+ ADDMENUITEM_P(ICON_MaxAccE, GET_TEXT(MSG_AMAX_E), onDrawMaxAccelE, SetMaxAccelE, &planner.settings.max_acceleration_mm_per_s2[E_AXIS]);
+ #endif
+ }
+ CurrentMenu->Draw();
+}
+
+#if HAS_CLASSIC_JERK
+ void Draw_MaxJerk_Menu() {
+ checkkey = Menu;
+ if (MaxJerkMenu == nullptr) MaxJerkMenu = new MenuClass();
+ if (CurrentMenu != MaxJerkMenu) {
+ CurrentMenu = MaxJerkMenu;
+ SetMenuTitle({1, 16, 28, 13}, {144, 16, 46, 11}, GET_TEXT_F(MSG_JERK));
+ DWINUI::MenuItemsPrepare(5);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Motion_Menu);
+ ADDMENUITEM_P(ICON_MaxSpeedJerkX, GET_TEXT(MSG_VA_JERK), onDrawMaxJerkX, SetMaxJerkX, &planner.max_jerk[X_AXIS]);
+ ADDMENUITEM_P(ICON_MaxSpeedJerkY, GET_TEXT(MSG_VB_JERK), onDrawMaxJerkY, SetMaxJerkY, &planner.max_jerk[Y_AXIS]);
+ ADDMENUITEM_P(ICON_MaxSpeedJerkZ, GET_TEXT(MSG_VC_JERK), onDrawMaxJerkZ, SetMaxJerkZ, &planner.max_jerk[Z_AXIS]);
+ #if HAS_HOTEND
+ ADDMENUITEM_P(ICON_MaxSpeedJerkE, GET_TEXT(MSG_VE_JERK), onDrawMaxJerkE, SetMaxJerkE, &planner.max_jerk[E_AXIS]);
+ #endif
+ }
+ CurrentMenu->Draw();
+ }
+#endif
+
+void Draw_Steps_Menu() {
+ checkkey = Menu;
+ if (StepsMenu == nullptr) StepsMenu = new MenuClass();
+ if (CurrentMenu != StepsMenu) {
+ CurrentMenu = StepsMenu;
+ SetMenuTitle({1, 16, 28, 13}, {144, 16, 46, 11}, GET_TEXT_F(MSG_STEPS_PER_MM));
+ DWINUI::MenuItemsPrepare(5);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawBack, Draw_Motion_Menu);
+ ADDMENUITEM_P(ICON_StepX, GET_TEXT(MSG_A_STEPS), onDrawStepsX, SetStepsX, &planner.settings.axis_steps_per_mm[X_AXIS]);
+ ADDMENUITEM_P(ICON_StepY, GET_TEXT(MSG_B_STEPS), onDrawStepsY, SetStepsY, &planner.settings.axis_steps_per_mm[Y_AXIS]);
+ ADDMENUITEM_P(ICON_StepZ, GET_TEXT(MSG_C_STEPS), onDrawStepsZ, SetStepsZ, &planner.settings.axis_steps_per_mm[Z_AXIS]);
+ #if HAS_HOTEND
+ ADDMENUITEM_P(ICON_StepE, GET_TEXT(MSG_E_STEPS), onDrawStepsE, SetStepsE, &planner.settings.axis_steps_per_mm[E_AXIS]);
+ #endif
+ }
+ CurrentMenu->Draw();
+}
+
+#if HAS_HOTEND
+ void Draw_HotendPID_Menu() {
+ checkkey = Menu;
+ if (HotendPIDMenu == nullptr) HotendPIDMenu = new MenuClass();
+ if (CurrentMenu != HotendPIDMenu) {
+ CurrentMenu = HotendPIDMenu;
+ CurrentMenu->MenuTitle.SetCaption(F("Hotend PID Settings"));
+ DWINUI::MenuItemsPrepare(8);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawMenuItem, Draw_AdvancedSettings_Menu);
+ ADDMENUITEM(ICON_PIDNozzle, F("Hotend PID"), onDrawMenuItem, HotendPID);
+ ADDMENUITEM_P(ICON_PIDValue, F("Set" STR_KP), onDrawPFloat2Menu, SetKp, &thermalManager.temp_hotend[0].pid.Kp);
+ ADDMENUITEM_P(ICON_PIDValue, F("Set" STR_KI), onDrawPIDi, SetKi, &thermalManager.temp_hotend[0].pid.Ki);
+ ADDMENUITEM_P(ICON_PIDValue, F("Set" STR_KD), onDrawPIDd, SetKd, &thermalManager.temp_hotend[0].pid.Kd);
+ ADDMENUITEM_P(ICON_Temperature, GET_TEXT(MSG_TEMPERATURE), onDrawPIntMenu, SetHotendPidT, &HMI_data.HotendPidT);
+ ADDMENUITEM_P(ICON_PIDcycles, GET_TEXT(MSG_PID_CYCLE), onDrawPIntMenu, SetPidCycles, &HMI_data.PidCycles);
+ #if ENABLED(EEPROM_SETTINGS)
+ ADDMENUITEM(ICON_WriteEEPROM, GET_TEXT(MSG_STORE_EEPROM), onDrawMenuItem, WriteEeprom);
+ #endif
+ }
+ CurrentMenu->Draw();
+ }
+#endif
+
+#if HAS_HEATED_BED
+ void Draw_BedPID_Menu() {
+ checkkey = Menu;
+ if (BedPIDMenu == nullptr) BedPIDMenu = new MenuClass();
+ if (CurrentMenu != BedPIDMenu) {
+ CurrentMenu = BedPIDMenu;
+ CurrentMenu->MenuTitle.SetCaption(F("Bed PID Settings"));
+ DWINUI::MenuItemsPrepare(8);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawMenuItem, Draw_AdvancedSettings_Menu);
+ ADDMENUITEM(ICON_PIDNozzle, F("Bed PID"), onDrawMenuItem,BedPID);
+ ADDMENUITEM_P(ICON_PIDValue, F("Set" STR_KP), onDrawPFloat2Menu, SetKp, &thermalManager.temp_bed.pid.Kp);
+ ADDMENUITEM_P(ICON_PIDValue, F("Set" STR_KI), onDrawPIDi, SetKi, &thermalManager.temp_bed.pid.Ki);
+ ADDMENUITEM_P(ICON_PIDValue, F("Set" STR_KD), onDrawPIDd, SetKd, &thermalManager.temp_bed.pid.Kd);
+ ADDMENUITEM_P(ICON_Temperature, GET_TEXT(MSG_TEMPERATURE), onDrawPIntMenu, SetBedPidT, &HMI_data.BedPidT);
+ ADDMENUITEM_P(ICON_PIDcycles, GET_TEXT(MSG_PID_CYCLE), onDrawPIntMenu, SetPidCycles, &HMI_data.PidCycles);
+ #if ENABLED(EEPROM_SETTINGS)
+ ADDMENUITEM(ICON_WriteEEPROM, GET_TEXT(MSG_STORE_EEPROM), onDrawMenuItem, WriteEeprom);
+ #endif
+ }
+ CurrentMenu->Draw();
+ }
+#endif
+
+#if EITHER(HAS_BED_PROBE, BABYSTEPPING)
+ void Draw_ZOffsetWiz_Menu() {
+ checkkey = Menu;
+ if (ZOffsetWizMenu == nullptr) ZOffsetWizMenu = new MenuClass();
+ if (CurrentMenu != ZOffsetWizMenu) {
+ CurrentMenu = ZOffsetWizMenu;
+ CurrentMenu->MenuTitle.SetCaption(GET_TEXT_F(MSG_PROBE_WIZARD));
+ DWINUI::MenuItemsPrepare(4);
+ ADDMENUITEM(ICON_Back, GET_TEXT(MSG_BUTTON_BACK), onDrawMenuItem, Draw_Prepare_Menu);
+ ADDMENUITEM(ICON_Homing, GET_TEXT(MSG_AUTO_HOME), onDrawMenuItem, AutoHome);
+ ADDMENUITEM(ICON_MoveZ0, F("Move Z to Home"), onDrawMenuItem, SetMoveZto0);
+ ADDMENUITEM_P(ICON_Zoffset, GET_TEXT(MSG_ZPROBE_ZOFFSET), onDrawPFloat2Menu, SetZOffset, &BABY_Z_VAR);
+ }
+ CurrentMenu->Draw();
+ if (!axis_is_trusted(Z_AXIS)) ui.set_status_P(PSTR("WARNING: Z position is unknow, move Z to home"));
+ }
+#endif
+
+
+#endif // DWIN_CREALITY_LCD_ENHANCED
diff --git a/Marlin/src/lcd/e3v2/enhanced/dwin.h b/Marlin/src/lcd/e3v2/enhanced/dwin.h
new file mode 100644
index 0000000000..db4cc2121f
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/enhanced/dwin.h
@@ -0,0 +1,265 @@
+/**
+ * DWIN UI Enhanced implementation
+ * Author: Miguel A. Risco-Castillo
+ * Version: 3.6.1
+ * Date: 2021/08/29
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser 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 Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+#pragma once
+
+#include "../../../inc/MarlinConfigPre.h"
+#include "dwinui.h"
+#include "rotary_encoder.h"
+#include "../../../libs/BL24CXX.h"
+
+#if ANY(HAS_HOTEND, HAS_HEATED_BED, HAS_FAN) && PREHEAT_COUNT
+ #define HAS_PREHEAT 1
+ #if PREHEAT_COUNT < 2
+ #error "Creality DWIN requires two material preheat presets."
+ #endif
+#endif
+
+#if ANY(AUTO_BED_LEVELING_BILINEAR, AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_3POINT) && DISABLED(PROBE_MANUALLY)
+ #define HAS_ONESTEP_LEVELING 1
+#endif
+
+#if !HAS_BED_PROBE && ENABLED(BABYSTEPPING)
+ #define JUST_BABYSTEP 1
+#endif
+
+#if ANY(BABYSTEPPING, HAS_BED_PROBE, HAS_WORKSPACE_OFFSET)
+ #define HAS_ZOFFSET_ITEM 1
+#endif
+
+static constexpr size_t eeprom_data_size = 64;
+
+enum processID : uint8_t {
+ // Process ID
+ MainMenu,
+ Menu,
+ SetInt,
+ SetPInt,
+ SetIntNoDraw,
+ SetFloat,
+ SetPFloat,
+ SelectFile,
+ PrintProcess,
+ PrintDone,
+ Info,
+
+ // Popup Windows
+ Homing,
+ Leveling,
+ PauseOrStop,
+ FilamentPurge,
+ WaitResponse,
+ Locked,
+ NothingToDo,
+};
+
+enum pidresult_t : uint8_t {
+ PID_BAD_EXTRUDER_NUM,
+ PID_TEMP_TOO_HIGH,
+ PID_TUNING_TIMEOUT,
+ PID_EXTR_START,
+ PID_BED_START,
+ PID_DONE
+};
+
+// Picture ID
+#define Start_Process 0
+#define Language_English 1
+#define Language_Chinese 2
+
+#define DWIN_CHINESE 123
+#define DWIN_ENGLISH 0
+
+typedef struct {
+ int8_t Color[3]; // Color components
+ int8_t Preheat = 0; // Material Select 0: PLA, 1: ABS, 2: Custom
+ AxisEnum axis = X_AXIS; // Axis Select
+ int32_t MaxValue = 0; // Auxiliar max integer/scaled float value
+ int32_t MinValue = 0; // Auxiliar min integer/scaled float value
+ int8_t dp = 0; // Auxiliar decimal places
+ int32_t Value = 0; // Auxiliar integer / scaled float value
+ int16_t *P_Int = nullptr; // Auxiliar pointer to 16 bit integer variable
+ float *P_Float = nullptr; // Auxiliar pointer to float variable
+ void (*Apply)() = nullptr; // Auxiliar apply function
+ void (*LiveUpdate)() = nullptr; // Auxiliar live update function
+} HMI_value_t;
+
+typedef struct {
+ uint16_t Background_Color = Def_Background_Color;
+ uint16_t Cursor_color = Def_Cursor_color;
+ uint16_t TitleBg_color = Def_TitleBg_color;
+ uint16_t TitleTxt_color = Def_TitleTxt_color;
+ uint16_t Text_Color = Def_Text_Color;
+ uint16_t Selected_Color = Def_Selected_Color;
+ uint16_t SplitLine_Color = Def_SplitLine_Color;
+ uint16_t Highlight_Color = Def_Highlight_Color;
+ uint16_t StatusBg_Color = Def_StatusBg_Color;
+ uint16_t StatusTxt_Color = Def_StatusTxt_Color;
+ uint16_t PopupBg_color = Def_PopupBg_color;
+ uint16_t PopupTxt_Color = Def_PopupTxt_Color;
+ uint16_t AlertBg_Color = Def_AlertBg_Color;
+ uint16_t AlertTxt_Color = Def_AlertTxt_Color;
+ uint16_t PercentTxt_Color = Def_PercentTxt_Color;
+ uint16_t Barfill_Color = Def_Barfill_Color;
+ uint16_t Indicator_Color = Def_Indicator_Color;
+ uint16_t Coordinate_Color = Def_Coordinate_Color;
+ TERN_(HAS_HOTEND, int16_t HotendPidT = PREHEAT_1_TEMP_HOTEND);
+ TERN_(HAS_HOTEND, int16_t PidCycles = 10);
+ #ifdef PREHEAT_1_TEMP_BED
+ int16_t BedPidT = PREHEAT_1_TEMP_BED;
+ #endif
+ TERN_(PREVENT_COLD_EXTRUSION, int16_t ExtMinT = EXTRUDE_MINTEMP);
+} HMI_data_t;
+
+typedef struct {
+ uint8_t language;
+ bool pause_flag:1; // printing is paused
+ bool pause_action:1; // flag a pause action
+ bool print_finish:1; // print was finished
+ bool select_flag:1; // Popup button selected
+ bool home_flag:1; // homing in course
+ bool heat_flag:1; // 0: heating done 1: during heating
+ bool lock_flag:1; // 0: lock called from AdvSet 1: lock called from Tune
+} HMI_flag_t;
+
+extern HMI_value_t HMI_value;
+extern HMI_flag_t HMI_flag;
+extern HMI_data_t HMI_data;
+extern uint8_t checkkey;
+extern millis_t dwin_heat_time;
+
+// Popup windows
+void DWIN_Popup_Confirm(uint8_t icon, const char * const msg1, const char * const msg2);
+#if HAS_HOTEND || HAS_HEATED_BED
+ void DWIN_Popup_Temperature(const bool toohigh);
+#endif
+TERN_(HAS_HOTEND, void Popup_Window_ETempTooLow());
+void Popup_Window_Resume();
+
+// SD Card
+void HMI_SDCardInit();
+void HMI_SDCardUpdate();
+
+// Main Process
+//void Icon_print();
+//void Icon_control();
+//void Icon_leveling(bool value);
+
+// Other
+void Goto_PrintProcess();
+void Goto_Main_Menu();
+void update_variable();
+void Draw_Select_Highlight(const bool sel);
+void Draw_Status_Area(const bool with_update); // Status Area
+void Draw_Main_Area(); // Redraw main area;
+void DWIN_Redraw_screen(); // Redraw all screen elements
+void HMI_StartFrame(const bool with_update); // Prepare the menu view
+void HMI_MainMenu(); // Main process screen
+void HMI_SelectFile(); // File page
+void HMI_Printing(); // Print page
+void HMI_ReturnScreen(); // Return to previous screen before popups
+void ApplyExtMinT();
+void HMI_SetLanguageCache(); // Set the languaje image cache
+
+//void HMI_Leveling(); // Level the page
+//void HMI_LevBedCorners(); // Tramming menu
+//void HMI_Info(); // Information menu
+
+
+void HMI_Init();
+void HMI_Popup();
+void HMI_SaveProcessID(const uint8_t id);
+void HMI_AudioFeedback(const bool success=true);
+void DWIN_Startup();
+void DWIN_Update();
+void EachMomentUpdate();
+void DWIN_HandleScreen();
+void DWIN_DrawStatusLine(const uint16_t color, const uint16_t bgcolor, const char *text);
+void DWIN_StatusChanged(const char * const text);
+void DWIN_StatusChanged_P(PGM_P const text);
+void DWIN_StartHoming();
+void DWIN_CompletedHoming();
+#if HAS_MESH
+ void DWIN_MeshUpdate(const int8_t xpos, const int8_t ypos, const float zval);
+#endif
+void DWIN_MeshLevelingStart();
+void DWIN_CompletedLeveling();
+void DWIN_PidTuning(pidresult_t result);
+void DWIN_Print_Started(const bool sd = false);
+void DWIN_Print_Finished();
+#if HAS_FILAMENT_SENSOR
+ void DWIN_FilamentRunout(const uint8_t extruder);
+#endif
+void DWIN_Progress_Update();
+void DWIN_Print_Header(const char *text);
+void DWIN_SetColorDefaults();
+void DWIN_StoreSettings(char *buff);
+void DWIN_LoadSettings(const char *buff);
+void DWIN_SetDataDefaults();
+void DWIN_RebootScreen();
+
+#if ENABLED(ADVANCED_PAUSE_FEATURE)
+ void Draw_Popup_FilamentPurge();
+ void DWIN_Popup_FilamentPurge();
+ void HMI_FilamentPurge();
+#endif
+
+// Utility and extensions
+void HMI_LockScreen();
+void DWIN_LockScreen(const bool flag = true);
+
+// HMI user control functions
+void HMI_Menu();
+void HMI_SetInt();
+void HMI_SetPInt();
+void HMI_SetIntNoDraw();
+void HMI_SetFloat();
+void HMI_SetPFloat();
+
+// Menu drawing functions
+void Draw_Control_Menu();
+void Draw_AdvancedSettings_Menu();
+void Draw_Prepare_Menu();
+void Draw_Move_Menu();
+void Draw_LevBedCorners_Menu();
+TERN_(HAS_HOME_OFFSET, void Draw_HomeOffset_Menu());
+TERN_(HAS_BED_PROBE, void Draw_ProbeSet_Menu());
+TERN_(HAS_FILAMENT_SENSOR, void Draw_FilSet_Menu());
+void Draw_SelectColors_Menu();
+void Draw_GetColor_Menu();
+void Draw_Tune_Menu();
+void Draw_Motion_Menu();
+TERN_(ADVANCED_PAUSE_FEATURE, void Draw_FilamentMan_Menu());
+TERN_(MESH_BED_LEVELING, void Draw_ManualMesh_Menu());
+#if HAS_HOTEND
+ void Draw_Preheat1_Menu();
+ void Draw_Preheat2_Menu();
+ void Draw_Preheat3_Menu();
+#endif
+void Draw_Temperature_Menu();
+void Draw_MaxSpeed_Menu();
+void Draw_MaxAccel_Menu();
+TERN_(HAS_CLASSIC_JERK, void Draw_MaxJerk_Menu());
+void Draw_Steps_Menu();
+TERN_(HAS_HOTEND, void Draw_HotendPID_Menu());
+TERN_(HAS_HEATED_BED, void Draw_BedPID_Menu());
+#if EITHER(HAS_BED_PROBE, BABYSTEPPING)
+ void Draw_ZOffsetWiz_Menu();
+#endif
diff --git a/Marlin/src/lcd/e3v2/enhanced/dwin_lcd.cpp b/Marlin/src/lcd/e3v2/enhanced/dwin_lcd.cpp
new file mode 100644
index 0000000000..b9246523ce
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/enhanced/dwin_lcd.cpp
@@ -0,0 +1,570 @@
+/**
+ * DWIN UI Enhanced implementation
+ * Author: Miguel A. Risco-Castillo
+ * Version: 3.6.1
+ * Date: 2021/08/29
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser 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 Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/********************************************************************************
+ * @file lcd/e3v2/enhanced/dwin_lcd.cpp
+ * @author LEO / Creality3D - Enhanced by Miguel A. Risco-Castillo
+ * @date 2021/09/08
+ * @version 2.2.1
+ * @brief DWIN screen control functions
+ ********************************************************************************/
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+
+#include "../../../inc/MarlinConfig.h"
+
+#include "dwin_lcd.h"
+#include // for memset
+
+//#define DEBUG_OUT 1
+#include "../../../core/debug_out.h"
+
+// Make sure DWIN_SendBuf is large enough to hold the largest string plus draw command and tail.
+// Assume the narrowest (6 pixel) font and 2-byte gb2312-encoded characters.
+uint8_t DWIN_SendBuf[11 + DWIN_DataLength] = { 0xAA };
+uint8_t DWIN_BufTail[4] = { 0xCC, 0x33, 0xC3, 0x3C };
+uint8_t databuf[26] = { 0 };
+uint8_t receivedType;
+
+int recnum = 0;
+
+inline void DWIN_Byte(size_t &i, const uint16_t bval) {
+ DWIN_SendBuf[++i] = bval;
+}
+
+inline void DWIN_Word(size_t &i, const uint16_t wval) {
+ DWIN_SendBuf[++i] = wval >> 8;
+ DWIN_SendBuf[++i] = wval & 0xFF;
+}
+
+inline void DWIN_Long(size_t &i, const uint32_t lval) {
+ DWIN_SendBuf[++i] = (lval >> 24) & 0xFF;
+ DWIN_SendBuf[++i] = (lval >> 16) & 0xFF;
+ DWIN_SendBuf[++i] = (lval >> 8) & 0xFF;
+ DWIN_SendBuf[++i] = lval & 0xFF;
+}
+
+inline void DWIN_String(size_t &i, const char * const string, uint16_t rlimit = 0xFFFF) {
+ if (!string) return;
+ const size_t len = _MIN(sizeof(DWIN_SendBuf) - i, _MIN(strlen(string), rlimit));
+ if (len == 0) return;
+ memcpy(&DWIN_SendBuf[i+1], string, len);
+ i += len;
+}
+
+inline void DWIN_String(size_t &i, const __FlashStringHelper * string, uint16_t rlimit = 0xFFFF) {
+ if (!string) return;
+ const size_t len = _MIN(sizeof(DWIN_SendBuf) - i, _MIN(rlimit, strlen_P((PGM_P)string))); // cast it to PGM_P, which is basically const char *, and measure it using the _P version of strlen.
+ if (len == 0) return;
+ memcpy(&DWIN_SendBuf[i+1], string, len);
+ i += len;
+}
+
+// Send the data in the buffer and the packet end
+inline void DWIN_Send(size_t &i) {
+ ++i;
+ LOOP_L_N(n, i) { LCD_SERIAL.write(DWIN_SendBuf[n]); delayMicroseconds(1); }
+ LOOP_L_N(n, 4) { LCD_SERIAL.write(DWIN_BufTail[n]); delayMicroseconds(1); }
+}
+
+/*-------------------------------------- System variable function --------------------------------------*/
+
+// Handshake (1: Success, 0: Fail)
+bool DWIN_Handshake(void) {
+ #ifndef LCD_BAUDRATE
+ #define LCD_BAUDRATE 115200
+ #endif
+ LCD_SERIAL.begin(LCD_BAUDRATE);
+ const millis_t serial_connect_timeout = millis() + 1000UL;
+ while (!LCD_SERIAL.connected() && PENDING(millis(), serial_connect_timeout)) { /*nada*/ }
+
+ size_t i = 0;
+ DWIN_Byte(i, 0x00);
+ DWIN_Send(i);
+
+ while (LCD_SERIAL.available() > 0 && recnum < (signed)sizeof(databuf)) {
+ databuf[recnum] = LCD_SERIAL.read();
+ // ignore the invalid data
+ if (databuf[0] != FHONE) { // prevent the program from running.
+ if (recnum > 0) {
+ recnum = 0;
+ ZERO(databuf);
+ }
+ continue;
+ }
+ delay(10);
+ recnum++;
+ }
+
+ return ( recnum >= 3
+ && databuf[0] == FHONE
+ && databuf[1] == '\0'
+ && databuf[2] == 'O'
+ && databuf[3] == 'K' );
+}
+
+// Set screen display direction
+// dir: 0=0°, 1=90°, 2=180°, 3=270°
+void DWIN_Frame_SetDir(uint8_t dir) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x34);
+ DWIN_Byte(i, 0x5A);
+ DWIN_Byte(i, 0xA5);
+ DWIN_Byte(i, dir);
+ DWIN_Send(i);
+}
+
+// Update display
+void DWIN_UpdateLCD(void) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x3D);
+ DWIN_Send(i);
+}
+
+/*---------------------------------------- Drawing functions ----------------------------------------*/
+
+// Clear screen
+// color: Clear screen color
+void DWIN_Frame_Clear(const uint16_t color) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x01);
+ DWIN_Word(i, color);
+ DWIN_Send(i);
+}
+
+// Draw a point
+// color: point color
+// width: point width 0x01-0x0F
+// height: point height 0x01-0x0F
+// x,y: upper left point
+void DWIN_Draw_Point(uint16_t color, uint8_t width, uint8_t height, uint16_t x, uint16_t y) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x02);
+ DWIN_Word(i, color);
+ DWIN_Byte(i, width);
+ DWIN_Byte(i, height);
+ DWIN_Word(i, x);
+ DWIN_Word(i, y);
+ DWIN_Send(i);
+}
+
+// Draw a line
+// color: Line segment color
+// xStart/yStart: Start point
+// xEnd/yEnd: End point
+void DWIN_Draw_Line(uint16_t color, uint16_t xStart, uint16_t yStart, uint16_t xEnd, uint16_t yEnd) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x03);
+ DWIN_Word(i, color);
+ DWIN_Word(i, xStart);
+ DWIN_Word(i, yStart);
+ DWIN_Word(i, xEnd);
+ DWIN_Word(i, yEnd);
+ DWIN_Send(i);
+}
+
+// Draw a rectangle
+// mode: 0=frame, 1=fill, 2=XOR fill
+// color: Rectangle color
+// xStart/yStart: upper left point
+// xEnd/yEnd: lower right point
+void DWIN_Draw_Rectangle(uint8_t mode, uint16_t color,
+ uint16_t xStart, uint16_t yStart, uint16_t xEnd, uint16_t yEnd) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x05);
+ DWIN_Byte(i, mode);
+ DWIN_Word(i, color);
+ DWIN_Word(i, xStart);
+ DWIN_Word(i, yStart);
+ DWIN_Word(i, xEnd);
+ DWIN_Word(i, yEnd);
+ DWIN_Send(i);
+}
+
+// Move a screen area
+// mode: 0, circle shift; 1, translation
+// dir: 0=left, 1=right, 2=up, 3=down
+// dis: Distance
+// color: Fill color
+// xStart/yStart: upper left point
+// xEnd/yEnd: bottom right point
+void DWIN_Frame_AreaMove(uint8_t mode, uint8_t dir, uint16_t dis,
+ uint16_t color, uint16_t xStart, uint16_t yStart, uint16_t xEnd, uint16_t yEnd) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x09);
+ DWIN_Byte(i, (mode << 7) | dir);
+ DWIN_Word(i, dis);
+ DWIN_Word(i, color);
+ DWIN_Word(i, xStart);
+ DWIN_Word(i, yStart);
+ DWIN_Word(i, xEnd);
+ DWIN_Word(i, yEnd);
+ DWIN_Send(i);
+}
+
+/*---------------------------------------- Text related functions ----------------------------------------*/
+
+// Draw a string
+// widthAdjust: true=self-adjust character width; false=no adjustment
+// bShow: true=display background color; false=don't display background color
+// size: Font size
+// color: Character color
+// bColor: Background color
+// x/y: Upper-left coordinate of the string
+// *string: The string
+// rlimit: For draw less chars than string length use rlimit
+void DWIN_Draw_String(bool widthAdjust, bool bShow, uint8_t size, uint16_t color, uint16_t bColor, uint16_t x, uint16_t y, const char * const string, uint16_t rlimit) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x11);
+ // Bit 7: widthAdjust
+ // Bit 6: bShow
+ // Bit 5-4: Unused (0)
+ // Bit 3-0: size
+ DWIN_Byte(i, (widthAdjust * 0x80) | (bShow * 0x40) | size);
+ DWIN_Word(i, color);
+ DWIN_Word(i, bColor);
+ DWIN_Word(i, x);
+ DWIN_Word(i, y);
+ DWIN_String(i, string, rlimit);
+ DWIN_Send(i);
+}
+
+// Draw a positive integer
+// bShow: true=display background color; false=don't display background color
+// zeroFill: true=zero fill; false=no zero fill
+// zeroMode: 1=leading 0 displayed as 0; 0=leading 0 displayed as a space
+// size: Font size
+// color: Character color
+// bColor: Background color
+// iNum: Number of digits
+// x/y: Upper-left coordinate
+// value: Integer value
+void DWIN_Draw_IntValue(uint8_t bShow, bool zeroFill, uint8_t zeroMode, uint8_t size, uint16_t color,
+ uint16_t bColor, uint8_t iNum, uint16_t x, uint16_t y, long value) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x14);
+ // Bit 7: bshow
+ // Bit 6: 1 = signed; 0 = unsigned number;
+ // Bit 5: zeroFill
+ // Bit 4: zeroMode
+ // Bit 3-0: size
+ DWIN_Byte(i, (bShow * 0x80) | (zeroFill * 0x20) | (zeroMode * 0x10) | size);
+ DWIN_Word(i, color);
+ DWIN_Word(i, bColor);
+ DWIN_Byte(i, iNum);
+ DWIN_Byte(i, 0); // fNum
+ DWIN_Word(i, x);
+ DWIN_Word(i, y);
+ #if 0
+ for (char count = 0; count < 8; count++) {
+ DWIN_Byte(i, value);
+ value >>= 8;
+ if (!(value & 0xFF)) break;
+ }
+ #else
+ // Write a big-endian 64 bit integer
+ const size_t p = i + 1;
+ for (char count = 8; count--;) { // 7..0
+ ++i;
+ DWIN_SendBuf[p + count] = value;
+ value >>= 8;
+ }
+ #endif
+
+ DWIN_Send(i);
+}
+
+// Draw a positive floating point number
+// bShow: true=display background color; false=don't display background color
+// zeroFill: true=zero fill; false=no zero fill
+// zeroMode: 1=leading 0 displayed as 0; 0=leading 0 displayed as a space
+// size: Font size
+// color: Character color
+// bColor: Background color
+// iNum: Number of whole digits
+// fNum: Number of decimal digits
+// x/y: Upper-left point
+// value: Scaled positive float value
+void DWIN_Draw_FloatValue(uint8_t bShow, bool zeroFill, uint8_t zeroMode, uint8_t size, uint16_t color,
+ uint16_t bColor, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, long value) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x14);
+ DWIN_Byte(i, (bShow * 0x80) | (zeroFill * 0x20) | (zeroMode * 0x10) | size);
+ DWIN_Word(i, color);
+ DWIN_Word(i, bColor);
+ DWIN_Byte(i, iNum);
+ DWIN_Byte(i, fNum);
+ DWIN_Word(i, x);
+ DWIN_Word(i, y);
+ DWIN_Long(i, value);
+ DWIN_Send(i);
+}
+// value: positive float value
+void DWIN_Draw_FloatValue(uint8_t bShow, bool zeroFill, uint8_t zeroMode, uint8_t size, uint16_t color,
+ uint16_t bColor, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value) {
+ const long val = round(value * POW(10, fNum));
+ DWIN_Draw_FloatValue(bShow, zeroFill, zeroMode, size, color, bColor, iNum, fNum, x, y, val);
+}
+
+/*---------------------------------------- Picture related functions ----------------------------------------*/
+
+// Display QR code
+// The size of the QR code is (46*QR_Pixel)*(46*QR_Pixel) dot matrix
+// QR_Pixel: The pixel size occupied by each point of the QR code: 0x01-0x0F (1-16)
+// (Nx, Ny): The coordinates of the upper left corner displayed by the QR code
+// str: multi-bit data
+void DWIN_Draw_QR(uint8_t QR_Pixel, uint16_t x, uint16_t y, char *string) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x21);
+ DWIN_Word(i, x);
+ DWIN_Word(i, y);
+ DWIN_Byte(i, QR_Pixel);
+ DWIN_String(i, string);
+ DWIN_Send(i);
+}
+
+// Draw JPG and cached in #0 virtual display area
+// id: Picture ID
+void DWIN_JPG_ShowAndCache(const uint8_t id) {
+ size_t i = 0;
+ DWIN_Word(i, 0x2200);
+ DWIN_Byte(i, id);
+ DWIN_Send(i);
+}
+
+// Draw an Icon
+// IBD: The icon background display: 0=Background filtering is not displayed, 1=Background display \\When setting the background filtering not to display, the background must be pure black
+// BIR: Background image restoration: 0=Background image is not restored, 1=Automatically use virtual display area image for background restoration
+// BFI: Background filtering strength: 0=normal, 1=enhanced, (only valid when the icon background display=0)
+// libID: Icon library ID
+// picID: Icon ID
+// x/y: Upper-left point
+void DWIN_ICON_Show(uint8_t IBD, uint8_t BIR, uint8_t BFI, uint8_t libID, uint8_t picID, uint16_t x, uint16_t y) {
+ NOMORE(x, DWIN_WIDTH - 1);
+ NOMORE(y, DWIN_HEIGHT - 1); // -- ozy -- srl
+ size_t i = 0;
+ DWIN_Byte(i, 0x23);
+ DWIN_Word(i, x);
+ DWIN_Word(i, y);
+ DWIN_Byte(i, IBD%2<<7 | BIR%2<<6 | BFI%2<<5 | libID);
+ DWIN_Byte(i, picID);
+ DWIN_Send(i);
+}
+
+// Draw an Icon from SRAM
+// IBD: The icon background display: 0=Background filtering is not displayed, 1=Background display \\When setting the background filtering not to display, the background must be pure black
+// BIR: Background image restoration: 0=Background image is not restored, 1=Automatically use virtual display area image for background restoration
+// BFI: Background filtering strength: 0=normal, 1=enhanced, (only valid when the icon background display=0)
+// x/y: Upper-left point
+// addr: SRAM address
+void DWIN_ICON_Show(uint8_t IBD, uint8_t BIR, uint8_t BFI, uint16_t x, uint16_t y, uint16_t addr) {
+ NOMORE(x, DWIN_WIDTH - 1);
+ NOMORE(y, DWIN_HEIGHT - 1); // -- ozy -- srl
+ size_t i = 0;
+ DWIN_Byte(i, 0x24);
+ DWIN_Word(i, x);
+ DWIN_Word(i, y);
+ DWIN_Byte(i, IBD%2<<7 | BIR%2<<6 | BFI%2<<5 | 0x00);
+ DWIN_Word(i, addr);
+ DWIN_Send(i);
+}
+
+// Unzip the JPG picture to a virtual display area
+// n: Cache index
+// id: Picture ID
+void DWIN_JPG_CacheToN(uint8_t n, uint8_t id) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x25);
+ DWIN_Byte(i, n);
+ DWIN_Byte(i, id);
+ DWIN_Send(i);
+}
+
+// Copy area from current virtual display area to current screen
+// xStart/yStart: Upper-left of virtual area
+// xEnd/yEnd: Lower-right of virtual area
+// x/y: Screen paste point
+void DWIN_Frame_AreaCopy(uint16_t xStart, uint16_t yStart,
+ uint16_t xEnd, uint16_t yEnd, uint16_t x, uint16_t y) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x26);
+ DWIN_Word(i, xStart);
+ DWIN_Word(i, yStart);
+ DWIN_Word(i, xEnd);
+ DWIN_Word(i, yEnd);
+ DWIN_Word(i, x);
+ DWIN_Word(i, y);
+ DWIN_Send(i);
+}
+
+// Copy area from virtual display area to current screen
+// IBD: background display: 0=Background filtering is not displayed, 1=Background display \\When setting the background filtering not to display, the background must be pure black
+// BIR: Background image restoration: 0=Background image is not restored, 1=Automatically use virtual display area image for background restoration
+// BFI: Background filtering strength: 0=normal, 1=enhanced, (only valid when the icon background display=0)
+// cacheID: virtual area number
+// xStart/yStart: Upper-left of virtual area
+// xEnd/yEnd: Lower-right of virtual area
+// x/y: Screen paste point
+void DWIN_Frame_AreaCopy(uint8_t IBD, uint8_t BIR, uint8_t BFI, uint8_t cacheID, uint16_t xStart, uint16_t yStart,
+ uint16_t xEnd, uint16_t yEnd, uint16_t x, uint16_t y) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x27);
+ DWIN_Byte(i, IBD%2<<7 | BIR%2<<6 | BFI%2<<5 | cacheID);
+ DWIN_Word(i, xStart);
+ DWIN_Word(i, yStart);
+ DWIN_Word(i, xEnd);
+ DWIN_Word(i, yEnd);
+ DWIN_Word(i, x);
+ DWIN_Word(i, y);
+ DWIN_Send(i);
+}
+
+// Animate a series of icons
+// animID: Animation ID; 0x00-0x0F
+// animate: true on; false off;
+// libID: Icon library ID
+// picIDs: Icon starting ID
+// picIDe: Icon ending ID
+// x/y: Upper-left point
+// interval: Display time interval, unit 10mS
+void DWIN_ICON_Animation(uint8_t animID, bool animate, uint8_t libID, uint8_t picIDs, uint8_t picIDe, uint16_t x, uint16_t y, uint16_t interval) {
+ NOMORE(x, DWIN_WIDTH - 1);
+ NOMORE(y, DWIN_HEIGHT - 1); // -- ozy -- srl
+ size_t i = 0;
+ DWIN_Byte(i, 0x28);
+ DWIN_Word(i, x);
+ DWIN_Word(i, y);
+ // Bit 7: animation on or off
+ // Bit 6: start from begin or end
+ // Bit 5-4: unused (0)
+ // Bit 3-0: animID
+ DWIN_Byte(i, (animate * 0x80) | 0x40 | animID);
+ DWIN_Byte(i, libID);
+ DWIN_Byte(i, picIDs);
+ DWIN_Byte(i, picIDe);
+ DWIN_Byte(i, interval);
+ DWIN_Send(i);
+}
+
+// Animation Control
+// state: 16 bits, each bit is the state of an animation id
+void DWIN_ICON_AnimationControl(uint16_t state) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x29);
+ DWIN_Word(i, state);
+ DWIN_Send(i);
+}
+
+// Set LCD Brightness 0x00-0xFF
+void DWIN_LCD_Brightness(const uint8_t brightness) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x30);
+ DWIN_Byte(i, brightness);
+ DWIN_Send(i);
+}
+
+// Write buffer data to the SRAM or Flash
+// mem: 0x5A=32KB SRAM, 0xA5=16KB Flash
+// addr: start address
+// length: Bytes to write
+// data: address of the buffer with data
+void DWIN_WriteToMem(uint8_t mem, uint16_t addr, uint16_t length, uint8_t *data) {
+ const uint8_t max_size = 128;
+ uint16_t pending = length;
+ uint16_t to_send;
+ uint16_t indx;
+ uint8_t block = 0;
+
+ while (pending > 0) {
+ indx = block * max_size;
+ to_send = _MIN(pending, max_size);
+ size_t i = 0;
+ DWIN_Byte(i, 0x31);
+ DWIN_Byte(i, mem);
+ DWIN_Word(i, addr + indx); // start address of the data block
+ ++i;
+ LOOP_L_N(j, i) { LCD_SERIAL.write(DWIN_SendBuf[j]); delayMicroseconds(1); } // Buf header
+ for (uint16_t j = indx; j <= indx + to_send - 1; j++) LCD_SERIAL.write(*(data + j)); delayMicroseconds(1); // write block of data
+ LOOP_L_N(j, 4) { LCD_SERIAL.write(DWIN_BufTail[j]); delayMicroseconds(1); }
+ block++;
+ pending -= to_send;
+ }
+}
+
+// Write the contents of the 32KB SRAM data memory into the designated image memory space.
+// picID: Picture memory space location, 0x00-0x0F, each space is 32Kbytes
+void DWIN_SRAMToPic(uint8_t picID) {
+ size_t i = 0;
+ DWIN_Byte(i, 0x33);
+ DWIN_Byte(i, 0x5A);
+ DWIN_Byte(i, 0xA5);
+ DWIN_Byte(i, picID);
+ DWIN_Send(i);
+}
+
+//--------------------------Test area -------------------------
+
+// void DWIN_ReadSRAM(uint16_t addr, uint8_t length, const char * const data) {
+// size_t i = 0;
+// DWIN_Byte(i, 0x32);
+// DWIN_Byte(i, 0x5A); // 0x5A Read from SRAM - 0xA5 Read from Flash
+// DWIN_Word(i, addr); // 0x0000 to 0x7FFF
+// const size_t len = _MIN(0xF0, length);
+// DWIN_Byte(i, len);
+// DWIN_Send(i);
+// }
+
+/*---------------------------------------- Memory functions ----------------------------------------*/
+// The LCD has an additional 32KB SRAM and 16KB Flash
+
+// Data can be written to the sram and save to one of the jpeg page files
+
+// Write Data Memory
+// command 0x31
+// Type: Write memory selection; 0x5A=SRAM; 0xA5=Flash
+// Address: Write data memory address; 0x000-0x7FFF for SRAM; 0x000-0x3FFF for Flash
+// Data: data
+//
+// Flash writing returns 0xA5 0x4F 0x4B
+
+// Read Data Memory
+// command 0x32
+// Type: Read memory selection; 0x5A=SRAM; 0xA5=Flash
+// Address: Read data memory address; 0x000-0x7FFF for SRAM; 0x000-0x3FFF for Flash
+// Length: leangth of data to read; 0x01-0xF0
+//
+// Response:
+// Type, Address, Length, Data
+
+// Write Picture Memory
+// Write the contents of the 32KB SRAM data memory into the designated image memory space
+// Issued: 0x5A, 0xA5, PIC_ID
+// Response: 0xA5 0x4F 0x4B
+//
+// command 0x33
+// 0x5A, 0xA5
+// PicId: Picture Memory location, 0x00-0x0F
+//
+// Flash writing returns 0xA5 0x4F 0x4B
+
+#endif // DWIN_CREALITY_LCD_ENHANCED
diff --git a/Marlin/src/lcd/e3v2/enhanced/dwin_lcd.h b/Marlin/src/lcd/e3v2/enhanced/dwin_lcd.h
new file mode 100644
index 0000000000..c66416a7ed
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/enhanced/dwin_lcd.h
@@ -0,0 +1,285 @@
+/**
+ * DWIN UI Enhanced implementation
+ * Author: Miguel A. Risco-Castillo
+ * Version: 3.6.1
+ * Date: 2021/08/29
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser 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 Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/********************************************************************************
+ * @file lcd/e3v2/enhanced/dwin_lcd.h
+ * @author LEO / Creality3D - Enhanced by Miguel A. Risco-Castillo
+ * @date 2021/08/09
+ * @version 2.2.1
+ * @brief DWIN screen control functions
+ ********************************************************************************/
+
+#pragma once
+
+#include
+
+#define RECEIVED_NO_DATA 0x00
+#define RECEIVED_SHAKE_HAND_ACK 0x01
+
+#define FHONE 0xAA
+
+#define DWIN_SCROLL_UP 2
+#define DWIN_SCROLL_DOWN 3
+
+#define DWIN_WIDTH 272
+#define DWIN_HEIGHT 480
+
+#define DWIN_DataLength (DWIN_WIDTH / 6 * 2)
+
+/*-------------------------------------- System variable function --------------------------------------*/
+
+// Handshake (1: Success, 0: Fail)
+bool DWIN_Handshake(void);
+
+// Set the backlight luminance
+// luminance: (0x00-0xFF)
+void DWIN_Backlight_SetLuminance(const uint8_t luminance);
+
+// Set screen display direction
+// dir: 0=0°, 1=90°, 2=180°, 3=270°
+void DWIN_Frame_SetDir(uint8_t dir);
+
+// Update display
+void DWIN_UpdateLCD(void);
+
+/*---------------------------------------- Drawing functions ----------------------------------------*/
+
+// Clear screen
+// color: Clear screen color
+void DWIN_Frame_Clear(const uint16_t color);
+
+// Draw a point
+// color: point color
+// width: point width 0x01-0x0F
+// height: point height 0x01-0x0F
+// x,y: upper left point
+void DWIN_Draw_Point(uint16_t color, uint8_t width, uint8_t height, uint16_t x, uint16_t y);
+
+// Draw a line
+// color: Line segment color
+// xStart/yStart: Start point
+// xEnd/yEnd: End point
+void DWIN_Draw_Line(uint16_t color, uint16_t xStart, uint16_t yStart, uint16_t xEnd, uint16_t yEnd);
+
+// Draw a Horizontal line
+// color: Line segment color
+// xStart/yStart: Start point
+// xLength: Line Length
+inline void DWIN_Draw_HLine(uint16_t color, uint16_t xStart, uint16_t yStart, uint16_t xLength) {
+ DWIN_Draw_Line(color, xStart, yStart, xStart + xLength - 1, yStart);
+}
+
+// Draw a Vertical line
+// color: Line segment color
+// xStart/yStart: Start point
+// yLength: Line Length
+inline void DWIN_Draw_VLine(uint16_t color, uint16_t xStart, uint16_t yStart, uint16_t yLength) {
+ DWIN_Draw_Line(color, xStart, yStart, xStart, yStart + yLength - 1);
+}
+
+// Draw a rectangle
+// mode: 0=frame, 1=fill, 2=XOR fill
+// color: Rectangle color
+// xStart/yStart: upper left point
+// xEnd/yEnd: lower right point
+void DWIN_Draw_Rectangle(uint8_t mode, uint16_t color, uint16_t xStart, uint16_t yStart, uint16_t xEnd, uint16_t yEnd);
+
+// Draw a box
+// mode: 0=frame, 1=fill, 2=XOR fill
+// color: Rectangle color
+// xStart/yStart: upper left point
+// xSize/ySize: box size
+inline void DWIN_Draw_Box(uint8_t mode, uint16_t color, uint16_t xStart, uint16_t yStart, uint16_t xSize, uint16_t ySize) {
+ DWIN_Draw_Rectangle(mode, color, xStart, yStart, xStart + xSize - 1, yStart + ySize - 1);
+}
+
+// Move a screen area
+// mode: 0, circle shift; 1, translation
+// dir: 0=left, 1=right, 2=up, 3=down
+// dis: Distance
+// color: Fill color
+// xStart/yStart: upper left point
+// xEnd/yEnd: bottom right point
+void DWIN_Frame_AreaMove(uint8_t mode, uint8_t dir, uint16_t dis,
+ uint16_t color, uint16_t xStart, uint16_t yStart, uint16_t xEnd, uint16_t yEnd);
+
+/*---------------------------------------- Text related functions ----------------------------------------*/
+
+// Draw a string
+// widthAdjust: true=self-adjust character width; false=no adjustment
+// bShow: true=display background color; false=don't display background color
+// size: Font size
+// color: Character color
+// bColor: Background color
+// x/y: Upper-left coordinate of the string
+// *string: The string
+// rlimit: For draw less chars than string length use rlimit
+void DWIN_Draw_String(bool widthAdjust, bool bShow, uint8_t size, uint16_t color, uint16_t bColor, uint16_t x, uint16_t y, const char * const string, uint16_t rlimit = 0xFFFF);
+inline void DWIN_Draw_String(bool bShow, uint8_t size, uint16_t color, uint16_t bColor, uint16_t x, uint16_t y, const char * const string, uint16_t rlimit = 0xFFFF) {
+ DWIN_Draw_String(0, bShow, size, color, bColor, x, y, string, rlimit);
+}
+
+class __FlashStringHelper;
+
+inline void DWIN_Draw_String(bool widthAdjust, bool bShow, uint8_t size, uint16_t color, uint16_t bColor, uint16_t x, uint16_t y, const __FlashStringHelper *title) {
+ DWIN_Draw_String(widthAdjust, bShow, size, color, bColor, x, y, (char *)title);
+}
+inline void DWIN_Draw_String(bool bShow, uint8_t size, uint16_t color, uint16_t bColor, uint16_t x, uint16_t y, const __FlashStringHelper *title) {
+ DWIN_Draw_String(0, bShow, size, color, bColor, x, y, (char *)title);
+}
+
+// Draw a positive integer
+// bShow: true=display background color; false=don't display background color
+// zeroFill: true=zero fill; false=no zero fill
+// zeroMode: 1=leading 0 displayed as 0; 0=leading 0 displayed as a space
+// size: Font size
+// color: Character color
+// bColor: Background color
+// iNum: Number of digits
+// x/y: Upper-left coordinate
+// value: Integer value
+void DWIN_Draw_IntValue(uint8_t bShow, bool zeroFill, uint8_t zeroMode, uint8_t size, uint16_t color,
+ uint16_t bColor, uint8_t iNum, uint16_t x, uint16_t y, long value);
+
+// Draw a positive floating point number
+// bShow: true=display background color; false=don't display background color
+// zeroFill: true=zero fill; false=no zero fill
+// zeroMode: 1=leading 0 displayed as 0; 0=leading 0 displayed as a space
+// size: Font size
+// color: Character color
+// bColor: Background color
+// iNum: Number of whole digits
+// fNum: Number of decimal digits
+// x/y: Upper-left point
+// value: Scaled positive float value
+void DWIN_Draw_FloatValue(uint8_t bShow, bool zeroFill, uint8_t zeroMode, uint8_t size, uint16_t color,
+ uint16_t bColor, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, long value);
+// value: positive float value
+void DWIN_Draw_FloatValue(uint8_t bShow, bool zeroFill, uint8_t zeroMode, uint8_t size, uint16_t color,
+ uint16_t bColor, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value);
+
+/*---------------------------------------- Picture related functions ----------------------------------------*/
+
+// Display QR code
+// The size of the QR code is (46*QR_Pixel)*(46*QR_Pixel) dot matrix
+// QR_Pixel: The pixel size occupied by each point of the QR code: 0x01-0x0F (1-16)
+// (Nx, Ny): The coordinates of the upper left corner displayed by the QR code
+// str: multi-bit data
+void DWIN_Draw_QR(uint8_t QR_Pixel, uint16_t x, uint16_t y, char *string);
+
+inline void DWIN_Draw_QR(uint8_t QR_Pixel, uint16_t x, uint16_t y, const __FlashStringHelper *title) {
+ DWIN_Draw_QR(QR_Pixel, x, y, (char *)title);
+}
+
+// Draw JPG and cached in #0 virtual display area
+// id: Picture ID
+void DWIN_JPG_ShowAndCache(const uint8_t id);
+
+// Draw an Icon
+// IBD: The icon background display: 0=Background filtering is not displayed, 1=Background display \\When setting the background filtering not to display, the background must be pure black
+// BIR: Background image restoration: 0=Background image is not restored, 1=Automatically use virtual display area image for background restoration
+// BFI: Background filtering strength: 0=normal, 1=enhanced, (only valid when the icon background display=0)
+// libID: Icon library ID
+// picID: Icon ID
+// x/y: Upper-left point
+void DWIN_ICON_Show(uint8_t IBD, uint8_t BIR, uint8_t BFI, uint8_t libID, uint8_t picID, uint16_t x, uint16_t y);
+
+// Draw an Icon with transparent background
+// libID: Icon library ID
+// picID: Icon ID
+// x/y: Upper-left point
+inline void DWIN_ICON_Show(uint8_t libID, uint8_t picID, uint16_t x, uint16_t y) {
+ DWIN_ICON_Show(0, 0, 1, libID, picID, x, y);
+}
+
+// Draw an Icon from SRAM
+// IBD: The icon background display: 0=Background filtering is not displayed, 1=Background display \\When setting the background filtering not to display, the background must be pure black
+// BIR: Background image restoration: 0=Background image is not restored, 1=Automatically use virtual display area image for background restoration
+// BFI: Background filtering strength: 0=normal, 1=enhanced, (only valid when the icon background display=0)
+// x/y: Upper-left point
+// addr: SRAM address
+void DWIN_ICON_Show(uint8_t IBD, uint8_t BIR, uint8_t BFI, uint16_t x, uint16_t y, uint16_t addr);
+
+// Unzip the JPG picture to a virtual display area
+// n: Cache index
+// id: Picture ID
+void DWIN_JPG_CacheToN(uint8_t n, uint8_t id);
+
+// Unzip the JPG picture to virtual display area #1
+// id: Picture ID
+inline void DWIN_JPG_CacheTo1(uint8_t id) { DWIN_JPG_CacheToN(1, id); }
+
+// Copy area from current virtual display area to current screen
+// xStart/yStart: Upper-left of virtual area
+// xEnd/yEnd: Lower-right of virtual area
+// x/y: Screen paste point
+void DWIN_Frame_AreaCopy(uint16_t xStart, uint16_t yStart,
+ uint16_t xEnd, uint16_t yEnd, uint16_t x, uint16_t y);
+
+// Copy area from virtual display area to current screen
+// IBD: background display: 0=Background filtering is not displayed, 1=Background display \\When setting the background filtering not to display, the background must be pure black
+// BIR: Background image restoration: 0=Background image is not restored, 1=Automatically use virtual display area image for background restoration
+// BFI: Background filtering strength: 0=normal, 1=enhanced, (only valid when the icon background display=0)
+// cacheID: virtual area number
+// xStart/yStart: Upper-left of virtual area
+// xEnd/yEnd: Lower-right of virtual area
+// x/y: Screen paste point
+void DWIN_Frame_AreaCopy(uint8_t IBD, uint8_t BIR, uint8_t BFI, uint8_t cacheID, uint16_t xStart, uint16_t yStart,
+ uint16_t xEnd, uint16_t yEnd, uint16_t x, uint16_t y);
+
+// Copy area from virtual display area to current screen with transparent background
+// cacheID: virtual area number
+// xStart/yStart: Upper-left of virtual area
+// xEnd/yEnd: Lower-right of virtual area
+// x/y: Screen paste point
+inline void DWIN_Frame_AreaCopy(uint8_t cacheID, uint16_t xStart, uint16_t yStart,
+ uint16_t xEnd, uint16_t yEnd, uint16_t x, uint16_t y) {
+ DWIN_Frame_AreaCopy(0, 0, 1, cacheID, xStart, yStart, xEnd, yEnd, x, y);
+}
+
+// Animate a series of icons
+// animID: Animation ID up to 16
+// animate: animation on or off
+// libID: Icon library ID
+// picIDs: Icon starting ID
+// picIDe: Icon ending ID
+// x/y: Upper-left point
+// interval: Display time interval, unit 10mS
+void DWIN_ICON_Animation(uint8_t animID, bool animate, uint8_t libID, uint8_t picIDs,
+ uint8_t picIDe, uint16_t x, uint16_t y, uint16_t interval);
+
+// Animation Control
+// state: 16 bits, each bit is the state of an animation id
+void DWIN_ICON_AnimationControl(uint16_t state);
+
+// Set LCD Brightness 0x00-0x0F
+void DWIN_LCD_Brightness(const uint8_t brightness);
+
+// Write buffer data to the SRAM or Flash
+// mem: 0x5A=32KB SRAM, 0xA5=16KB Flash
+// addr: start address
+// length: Bytes to write
+// data: address of the buffer with data
+void DWIN_WriteToMem(uint8_t mem, uint16_t addr, uint16_t length, uint8_t *data);
+
+// Write the contents of the 32KB SRAM data memory into the designated image memory space.
+// picID: Picture memory space location, 0x00-0x0F, each space is 32Kbytes
+void DWIN_SRAMToPic(uint8_t picID);
diff --git a/Marlin/src/lcd/e3v2/enhanced/dwinui.cpp b/Marlin/src/lcd/e3v2/enhanced/dwinui.cpp
new file mode 100644
index 0000000000..85353bed28
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/enhanced/dwinui.cpp
@@ -0,0 +1,452 @@
+/**
+ * DWIN UI Enhanced implementation
+ * Author: Miguel A. Risco-Castillo
+ * Version: 3.6.3
+ * Date: 2021/08/09
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser 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 Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+
+#include "../../../inc/MarlinConfig.h"
+#include "../../../core/macros.h"
+#include "dwin_lcd.h"
+#include "dwinui.h"
+
+//#define DEBUG_OUT 1
+#include "../../../core/debug_out.h"
+
+uint8_t MenuItemTotal = 0;
+uint8_t MenuItemCount = 0;
+MenuItemClass** MenuItems = nullptr;
+MenuClass *CurrentMenu = nullptr;
+MenuClass *PreviousMenu = nullptr;
+
+xy_int_t DWINUI::cursor = { 0 };
+uint16_t DWINUI::pencolor = Color_White;
+uint16_t DWINUI::textcolor = Def_Text_Color;
+uint16_t DWINUI::backcolor = Def_Background_Color;
+uint8_t DWINUI::font = font8x16;
+
+void (*DWINUI::onCursorErase)(uint8_t line)=nullptr;
+void (*DWINUI::onCursorDraw)(uint8_t line)=nullptr;
+void (*DWINUI::onTitleDraw)(TitleClass* title)=nullptr;
+void (*DWINUI::onMenuDraw)(MenuClass* menu)=nullptr;
+
+void DWINUI::Init(void) {
+ DEBUG_ECHOPGM("\r\nDWIN handshake ");
+ delay(750); // Delay here or init later in the boot process
+ const bool success = DWIN_Handshake();
+ if (success) DEBUG_ECHOLNPGM("ok."); else DEBUG_ECHOLNPGM("error.");
+ DWIN_Frame_SetDir(1);
+ TERN(SHOW_BOOTSCREEN,,DWIN_Frame_Clear(Color_Bg_Black));
+ DWIN_UpdateLCD();
+ cursor.x = 0;
+ cursor.y = 0;
+ pencolor = Color_White;
+ textcolor = Def_Text_Color;
+ backcolor = Def_Background_Color;
+ font = font8x16;
+}
+
+// Set text/number font
+void DWINUI::SetFont(uint8_t cfont) {
+ font = cfont;
+}
+
+// Get font character width
+uint8_t DWINUI::Get_font_width(uint8_t cfont) {
+ switch (cfont) {
+ case font6x12 : return 6;
+ case font8x16 : return 8;
+ case font10x20: return 10;
+ case font12x24: return 12;
+ case font14x28: return 14;
+ case font16x32: return 16;
+ case font20x40: return 20;
+ case font24x48: return 24;
+ case font28x56: return 28;
+ case font32x64: return 32;
+ default: return 0;
+ }
+}
+
+// Get font character heigh
+uint8_t DWINUI::Get_font_height(uint8_t cfont) {
+ switch (cfont) {
+ case font6x12 : return 12;
+ case font8x16 : return 16;
+ case font10x20: return 20;
+ case font12x24: return 24;
+ case font14x28: return 28;
+ case font16x32: return 32;
+ case font20x40: return 40;
+ case font24x48: return 48;
+ case font28x56: return 56;
+ case font32x64: return 64;
+ default: return 0;
+ }
+}
+
+// Get screen x coodinates from text column
+uint16_t DWINUI::ColToX(uint8_t col) {
+ return col * Get_font_width(font);
+}
+
+// Get screen y coodinates from text row
+uint16_t DWINUI::RowToY(uint8_t row) {
+ return row * Get_font_height(font);
+}
+
+// Set text/number color
+void DWINUI::SetColors(uint16_t fgcolor, uint16_t bgcolor) {
+ textcolor = fgcolor;
+ backcolor = bgcolor;
+}
+void DWINUI::SetTextColor(uint16_t fgcolor) {
+ textcolor = fgcolor;
+}
+void DWINUI::SetBackgroundColor(uint16_t bgcolor) {
+ backcolor = bgcolor;
+}
+
+// Moves cursor to point
+// x: abscissa of the display
+// y: ordinate of the display
+// point: xy coordinate
+void DWINUI::MoveTo(int16_t x, int16_t y) {
+ cursor.x = x;
+ cursor.y = y;
+}
+void DWINUI::MoveTo(xy_int_t point) {
+ cursor = point;
+}
+
+// Moves cursor relative to the actual position
+// x: abscissa of the display
+// y: ordinate of the display
+// point: xy coordinate
+void DWINUI::MoveBy(int16_t x, int16_t y) {
+ cursor.x += x;
+ cursor.y += y;
+}
+void DWINUI::MoveBy(xy_int_t point) {
+ cursor += point;
+}
+
+// Draw a Centered string using DWIN_WIDTH
+void DWINUI::Draw_CenteredString(bool bShow, uint8_t size, uint16_t color, uint16_t bColor, uint16_t y, const char * const string) {
+ const int8_t x = _MAX(0U, DWIN_WIDTH - strlen_P(string) * Get_font_width(size)) / 2 - 1;
+ DWIN_Draw_String(bShow, size, color, bColor, x, y, string);
+}
+
+// Draw a char at cursor position
+void DWINUI::Draw_Char(const char c) {
+ const char string[2] = { c, 0};
+ DWIN_Draw_String(false, font, textcolor, backcolor, cursor.x, cursor.y, string, 1);
+ MoveBy(Get_font_width(font), 0);
+}
+
+// Draw a string at cursor position
+// color: Character color
+// *string: The string
+// rlimit: For draw less chars than string length use rlimit
+void DWINUI::Draw_String(const char * const string, uint16_t rlimit) {
+ DWIN_Draw_String(false, font, textcolor, backcolor, cursor.x, cursor.y, string, rlimit);
+ MoveBy(strlen(string) * Get_font_width(font), 0);
+}
+void DWINUI::Draw_String(uint16_t color, const char * const string, uint16_t rlimit) {
+ DWIN_Draw_String(false, font, color, backcolor, cursor.x, cursor.y, string, rlimit);
+ MoveBy(strlen(string) * Get_font_width(font), 0);
+}
+
+// Draw a signed floating point number
+// bShow: true=display background color; false=don't display background color
+// zeroFill: true=zero fill; false=no zero fill
+// zeroMode: 1=leading 0 displayed as 0; 0=leading 0 displayed as a space
+// size: Font size
+// bColor: Background color
+// iNum: Number of whole digits
+// fNum: Number of decimal digits
+// x/y: Upper-left point
+// value: Float value
+void DWINUI::Draw_Signed_Float(uint8_t bShow, bool zeroFill, uint8_t zeroMode, uint8_t size, uint16_t color, uint16_t bColor, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value) {
+ if (value < 0) {
+ DWIN_Draw_FloatValue(bShow, zeroFill, zeroMode, size, color, bColor, iNum, fNum, x, y, -value);
+ DWIN_Draw_String(bShow, size, color, bColor, x - 6, y, F("-"));
+ }
+ else {
+ DWIN_Draw_String(bShow, size, color, bColor, x - 6, y, F(" "));
+ DWIN_Draw_FloatValue(bShow, zeroFill, zeroMode, size, color, bColor, iNum, fNum, x, y, value);
+ }
+}
+
+// Draw a circle
+// color: circle color
+// x: the abscissa of the center of the circle
+// y: ordinate of the center of the circle
+// r: circle radius
+void DWINUI::Draw_Circle(uint16_t color, uint16_t x, uint16_t y, uint8_t r) {
+ int a = 0, b = 0;
+ while (a <= b) {
+ b = SQRT(sq(r) - sq(a));
+ if (a == 0) b--;
+ DWIN_Draw_Point(color, 1, 1, x + a, y + b); // Draw some sector 1
+ DWIN_Draw_Point(color, 1, 1, x + b, y + a); // Draw some sector 2
+ DWIN_Draw_Point(color, 1, 1, x + b, y - a); // Draw some sector 3
+ DWIN_Draw_Point(color, 1, 1, x + a, y - b); // Draw some sector 4
+ DWIN_Draw_Point(color, 1, 1, x - a, y - b); // Draw some sector 5
+ DWIN_Draw_Point(color, 1, 1, x - b, y - a); // Draw some sector 6
+ DWIN_Draw_Point(color, 1, 1, x - b, y + a); // Draw some sector 7
+ DWIN_Draw_Point(color, 1, 1, x - a, y + b); // Draw some sector 8
+ a++;
+ }
+}
+
+// Draw a circle filled with color
+// bcolor: fill color
+// x: the abscissa of the center of the circle
+// y: ordinate of the center of the circle
+// r: circle radius
+void DWINUI::Draw_FillCircle(uint16_t bcolor, uint16_t x,uint16_t y,uint8_t r) {
+ int a = 0, b = 0;
+ while (a <= b) {
+ b = SQRT(sq(r) - sq(a)); // b=sqrt(r*r-a*a);
+ if (a == 0) b--;
+ DWIN_Draw_Line(bcolor, x-b,y-a,x+b,y-a);
+ DWIN_Draw_Line(bcolor, x-a,y-b,x+a,y-b);
+ DWIN_Draw_Line(bcolor, x-b,y+a,x+b,y+a);
+ DWIN_Draw_Line(bcolor, x-a,y+b,x+a,y+b);
+ a++;
+ }
+}
+
+// Color Interpolator
+// val : Interpolator minv..maxv
+// minv : Minimum value
+// maxv : Maximun value
+// color1 : Start color
+// color2 : End color
+uint16_t DWINUI::ColorInt(int16_t val, int16_t minv, int16_t maxv, uint16_t color1, uint16_t color2) {
+ uint8_t B,G,R;
+ float n;
+ n = (float)(val-minv)/(maxv-minv);
+ R = (1-n)*GetRColor(color1) + n*GetRColor(color2);
+ G = (1-n)*GetGColor(color1) + n*GetGColor(color2);
+ B = (1-n)*GetBColor(color1) + n*GetBColor(color2);
+ return RGB(R,G,B);
+}
+
+// Color Interpolator through Red->Yellow->Green->Blue
+// val : Interpolator minv..maxv
+// minv : Minimum value
+// maxv : Maximun value
+uint16_t DWINUI::RainbowInt(int16_t val, int16_t minv, int16_t maxv) {
+ uint8_t B,G,R;
+ const uint8_t maxB = 28;
+ const uint8_t maxR = 28;
+ const uint8_t maxG = 38;
+ const int16_t limv = _MAX(abs(minv), abs(maxv));
+ float n;
+ if (minv>=0) {
+ n = (float)(val-minv)/(maxv-minv);
+ } else {
+ n = (float)val/limv;
+ }
+ n = _MIN(1, n);
+ n = _MAX(-1, n);
+ if (n < 0) {
+ R = 0;
+ G = (1+n)*maxG;
+ B = (-n)*maxB;
+ } else if (n < 0.5) {
+ R = maxR*n*2;
+ G = maxG;
+ B = 0;
+ } else {
+ R = maxR;
+ G = maxG*(1-n);
+ B = 0;
+ }
+ return RGB(R,G,B);
+}
+
+// Draw a checkbox
+// Color: frame color
+// bColor: Background color
+// x/y: Upper-left point
+// mode : 0 : unchecked, 1 : checked
+void DWINUI::Draw_Checkbox(uint16_t color, uint16_t bcolor, uint16_t x, uint16_t y, bool checked=false) {
+ DWIN_Draw_String(false, true, font8x16, color, bcolor, x + 4, y, checked ? F("x") : F(" "));
+ DWIN_Draw_Rectangle(0, color, x + 2, y + 2, x + 17, y + 17);
+}
+
+// Clear Menu by filling the menu area with background color
+void DWINUI::ClearMenuArea() {
+ DWIN_Draw_Rectangle(1, backcolor, 0, TITLE_HEIGHT, DWIN_WIDTH - 1, STATUS_Y - 1);
+}
+
+void DWINUI::MenuItemsClear() {
+ if (MenuItems == nullptr) return;
+ for (uint8_t i = 0; i < MenuItemCount; i++) delete MenuItems[i];
+ delete[] MenuItems;
+ MenuItems = nullptr;
+ MenuItemCount = 0;
+ MenuItemTotal = 0;
+}
+
+void DWINUI::MenuItemsPrepare(uint8_t totalitems) {
+ MenuItemsClear();
+ MenuItemTotal = totalitems;
+ MenuItems = new MenuItemClass*[totalitems];
+}
+
+MenuItemClass* DWINUI::MenuItemsAdd(MenuItemClass* menuitem) {
+ if (MenuItemCount < MenuItemTotal) {
+ MenuItems[MenuItemCount] = menuitem;
+ menuitem->pos = MenuItemCount++;
+ return menuitem;
+ }
+ else {
+ delete menuitem;
+ return nullptr;
+ }
+}
+
+/* Title Class ==============================================================*/
+
+TitleClass Title;
+
+void TitleClass::Draw() {
+ if (DWINUI::onTitleDraw != nullptr) (*DWINUI::onTitleDraw)(this);
+}
+
+void TitleClass::SetCaption(const char * const title) {
+ frameid = 0;
+ if ( caption == title ) return;
+ const uint8_t len = _MIN(sizeof(caption) - 1, strlen(title));
+ memcpy(&caption[0], title, len);
+ caption[len] = '\0';
+}
+
+void TitleClass::ShowCaption(const char * const title) {
+ SetCaption(title);
+ Draw();
+}
+
+void TitleClass::SetFrame(uint8_t id, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
+ caption[0] = '\0';
+ frameid = id;
+ frame = { x1, y1, x2, y2 };
+}
+
+void TitleClass::SetFrame(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
+ SetFrame(1, x, y, x + w - 1, y + h - 1);
+}
+
+void TitleClass::FrameCopy(uint8_t id, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
+ SetFrame(id, x1, y1, x2, y2);
+ Draw();
+}
+
+void TitleClass::FrameCopy(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
+ FrameCopy(1, x, y, x + w - 1, y + h - 1);
+}
+
+/* Menu Class ===============================================================*/
+
+MenuClass::MenuClass() {
+ selected = 0;
+ topline = 0;
+}
+
+void MenuClass::Draw() {
+ MenuTitle.Draw();
+ if (DWINUI::onMenuDraw != nullptr) (*DWINUI::onMenuDraw)(this);
+ for (uint8_t i = 0; i < MenuItemCount; i++)
+ MenuItems[i]->Draw(i - topline);
+ if (DWINUI::onCursorDraw != nullptr) DWINUI::onCursorDraw(line());
+ DWIN_UpdateLCD();
+}
+
+void MenuClass::onScroll(bool dir) {
+ int8_t sel = selected;
+ if (dir) sel++; else sel--;
+ LIMIT(sel, 0, MenuItemCount - 1);
+ if (sel != selected) {
+ if (DWINUI::onCursorErase != nullptr) DWINUI::onCursorErase(line());
+ if ((sel - topline) == TROWS) {
+ DWIN_Frame_AreaMove(1, DWIN_SCROLL_UP, MLINE, DWINUI::backcolor, 0, TITLE_HEIGHT + 1, DWIN_WIDTH, STATUS_Y - 1);
+ topline++;
+ MenuItems[sel]->Draw(TROWS - 1);
+ }
+ if ((sel < topline)) {
+ DWIN_Frame_AreaMove(1, DWIN_SCROLL_DOWN, MLINE, DWINUI::backcolor, 0, TITLE_HEIGHT + 1, DWIN_WIDTH, STATUS_Y - 1);
+ topline--;
+ MenuItems[sel]->Draw(0);
+ }
+ selected = sel;
+ if (DWINUI::onCursorDraw != nullptr) DWINUI::onCursorDraw(line());
+ DWIN_UpdateLCD();
+ }
+}
+
+void MenuClass::onClick() {
+ if (MenuItems[selected]->onClick != nullptr) (*MenuItems[selected]->onClick)();
+}
+
+MenuItemClass *MenuClass::SelectedItem() {
+ return MenuItems[selected];
+}
+
+/* MenuItem Class ===========================================================*/
+
+MenuItemClass::MenuItemClass(uint8_t cicon, const char * const text, void (*ondraw)(MenuItemClass* menuitem, int8_t line), void (*onclick)()) {
+ icon = cicon;
+ onClick = onclick;
+ onDraw = ondraw;
+ const uint8_t len = _MIN(sizeof(caption) - 1, strlen(text));
+ memcpy(&caption[0], text, len);
+ caption[len] = '\0';
+}
+
+MenuItemClass::MenuItemClass(uint8_t cicon, uint8_t id, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, void (*ondraw)(MenuItemClass* menuitem, int8_t line), void (*onclick)()) {
+ icon = cicon;
+ onClick = onclick;
+ onDraw = ondraw;
+ caption[0] = '\0';
+ frameid = id;
+ frame = { x1, y1, x2, y2 };
+}
+
+void MenuItemClass::SetFrame(uint8_t id, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
+ caption[0] = '\0';
+ frameid = id;
+ frame = { x1, y1, x2, y2 };
+}
+
+void MenuItemClass::Draw(int8_t line) {
+ if (line < 0 || line >= TROWS) return;
+ if (onDraw != nullptr) (*onDraw)(this, line);
+};
+
+MenuItemPtrClass::MenuItemPtrClass(uint8_t cicon, const char * const text, void (*ondraw)(MenuItemClass* menuitem, int8_t line), void (*onclick)(), void* val) : MenuItemClass(cicon, text, ondraw, onclick) {
+ value = val;
+};
+
+#endif // DWIN_CREALITY_LCD_ENHANCED
diff --git a/Marlin/src/lcd/e3v2/enhanced/dwinui.h b/Marlin/src/lcd/e3v2/enhanced/dwinui.h
new file mode 100644
index 0000000000..1ec51bec22
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/enhanced/dwinui.h
@@ -0,0 +1,624 @@
+/**
+ * DWIN UI Enhanced implementation
+ * Author: Miguel A. Risco-Castillo
+ * Version: 3.6.3
+ * Date: 2021/08/09
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser 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 Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+#pragma once
+
+#include "../../../core/types.h"
+#include "dwin_lcd.h"
+
+// ICON ID
+#define ICON 7 // Icon set file 7.ICO
+
+#define ICON_LOGO 0
+#define ICON_Print_0 1
+#define ICON_Print_1 2
+#define ICON_Prepare_0 3
+#define ICON_Prepare_1 4
+#define ICON_Control_0 5
+#define ICON_Control_1 6
+#define ICON_Leveling_0 7
+#define ICON_Leveling_1 8
+#define ICON_HotendTemp 9
+#define ICON_BedTemp 10
+#define ICON_Speed 11
+#define ICON_Zoffset 12
+#define ICON_Back 13
+#define ICON_File 14
+#define ICON_PrintTime 15
+#define ICON_RemainTime 16
+#define ICON_Setup_0 17
+#define ICON_Setup_1 18
+#define ICON_Pause_0 19
+#define ICON_Pause_1 20
+#define ICON_Continue_0 21
+#define ICON_Continue_1 22
+#define ICON_Stop_0 23
+#define ICON_Stop_1 24
+#define ICON_Bar 25
+#define ICON_More 26
+
+#define ICON_Axis 27
+#define ICON_CloseMotor 28
+#define ICON_Homing 29
+#define ICON_SetHome 30
+#define ICON_PLAPreheat 31
+#define ICON_ABSPreheat 32
+#define ICON_Cool 33
+#define ICON_Language 34
+
+#define ICON_MoveX 35
+#define ICON_MoveY 36
+#define ICON_MoveZ 37
+#define ICON_Extruder 38
+
+#define ICON_Temperature 40
+#define ICON_Motion 41
+#define ICON_WriteEEPROM 42
+#define ICON_ReadEEPROM 43
+#define ICON_ResumeEEPROM 44
+#define ICON_Info 45
+
+#define ICON_SetEndTemp 46
+#define ICON_SetBedTemp 47
+#define ICON_FanSpeed 48
+#define ICON_SetPLAPreheat 49
+#define ICON_SetABSPreheat 50
+
+#define ICON_MaxSpeed 51
+#define ICON_MaxAccelerated 52
+#define ICON_MaxJerk 53
+#define ICON_Step 54
+#define ICON_PrintSize 55
+#define ICON_Version 56
+#define ICON_Contact 57
+#define ICON_StockConfiguration 58
+#define ICON_MaxSpeedX 59
+#define ICON_MaxSpeedY 60
+#define ICON_MaxSpeedZ 61
+#define ICON_MaxSpeedE 62
+#define ICON_MaxAccX 63
+#define ICON_MaxAccY 64
+#define ICON_MaxAccZ 65
+#define ICON_MaxAccE 66
+#define ICON_MaxSpeedJerkX 67
+#define ICON_MaxSpeedJerkY 68
+#define ICON_MaxSpeedJerkZ 69
+#define ICON_MaxSpeedJerkE 70
+#define ICON_StepX 71
+#define ICON_StepY 72
+#define ICON_StepZ 73
+#define ICON_StepE 74
+#define ICON_Setspeed 75
+#define ICON_SetZOffset 76
+#define ICON_Rectangle 77
+#define ICON_BLTouch 78
+#define ICON_TempTooLow 79
+#define ICON_AutoLeveling 80
+#define ICON_TempTooHigh 81
+#define ICON_NoTips_C 82
+#define ICON_NoTips_E 83
+#define ICON_Continue_C 84
+#define ICON_Continue_E 85
+#define ICON_Cancel_C 86
+#define ICON_Cancel_E 87
+#define ICON_Confirm_C 88
+#define ICON_Confirm_E 89
+#define ICON_Info_0 90
+#define ICON_Info_1 91
+
+// Extra Icons
+#define ICON_AdvSet ICON_Language
+#define ICON_Brightness ICON_Motion
+#define ICON_Cancel ICON_StockConfiguration
+#define ICON_CustomPreheat ICON_SetEndTemp
+#define ICON_Error ICON_TempTooHigh
+#define ICON_ExtrudeMinT ICON_HotendTemp
+#define ICON_FilLoad ICON_WriteEEPROM
+#define ICON_FilMan ICON_ResumeEEPROM
+#define ICON_FilSet ICON_ResumeEEPROM
+#define ICON_FilUnload ICON_ReadEEPROM
+#define ICON_Flow ICON_StepE
+#define ICON_HomeOffset ICON_AdvSet
+#define ICON_HomeOffsetX ICON_StepX
+#define ICON_HomeOffsetY ICON_StepY
+#define ICON_HomeOffsetZ ICON_StepZ
+#define ICON_LevBed ICON_SetEndTemp
+#define ICON_Lock ICON_Cool
+#define ICON_ManualMesh ICON_HotendTemp
+#define ICON_MeshNext ICON_Axis
+#define ICON_MeshSave ICON_WriteEEPROM
+#define ICON_MoveZ0 ICON_HotendTemp
+#define ICON_Park ICON_Motion
+#define ICON_PIDbed ICON_SetBedTemp
+#define ICON_PIDcycles ICON_ResumeEEPROM
+#define ICON_PIDNozzle ICON_SetEndTemp
+#define ICON_PIDValue ICON_Contact
+#define ICON_ProbeOffsetX ICON_StepX
+#define ICON_ProbeOffsetY ICON_StepY
+#define ICON_ProbeOffsetZ ICON_StepZ
+#define ICON_ProbeSet ICON_SetEndTemp
+#define ICON_ProbeTest ICON_SetEndTemp
+#define ICON_Pwrlossr ICON_Motion
+#define ICON_Reboot ICON_ResumeEEPROM
+#define ICON_Runout ICON_MaxAccE
+#define ICON_Scolor ICON_MaxSpeed
+#define ICON_SetCustomPreheat ICON_SetEndTemp
+#define ICON_Sound ICON_Cool
+
+/**
+ * 3-.0:The font size, 0x00-0x09, corresponds to the font size below:
+ * 0x00=6*12 0x01=8*16 0x02=10*20 0x03=12*24 0x04=14*28
+ * 0x05=16*32 0x06=20*40 0x07=24*48 0x08=28*56 0x09=32*64
+ */
+#define font6x12 0x00
+#define font8x16 0x01
+#define font10x20 0x02
+#define font12x24 0x03
+#define font14x28 0x04
+#define font16x32 0x05
+#define font20x40 0x06
+#define font24x48 0x07
+#define font28x56 0x08
+#define font32x64 0x09
+
+// Extended and default UI Colors
+#define RGB(R,G,B) (R << 11) | (G << 5) | (B) // R,B: 0..31; G: 0..63
+#define GetRColor(color) ((color >> 11) & 0x1F)
+#define GetGColor(color) ((color >> 5) & 0x3F)
+#define GetBColor(color) ((color >> 0) & 0x1F)
+
+#define Color_White 0xFFFF
+#define Color_Bg_Window 0x31E8 // Popup background color
+#define Color_Bg_Blue 0x1125 // Dark blue background color
+#define Color_Bg_Black 0x0841 // Black background color
+#define Color_Bg_Red 0xF00F // Red background color
+#define Popup_Text_Color 0xD6BA // Popup font background color
+#define Line_Color 0x3A6A // Split line color
+#define Rectangle_Color 0xEE2F // Blue square cursor color
+#define Percent_Color 0xFE29 // Percentage color
+#define BarFill_Color 0x10E4 // Fill color of progress bar
+#define Select_Color 0x33BB // Selected color
+
+#define Color_Black 0
+#define Color_Red RGB(31,0,0)
+#define Color_Yellow RGB(31,63,0)
+#define Color_Green RGB(0,63,0)
+#define Color_Aqua RGB(0,63,31)
+#define Color_Blue RGB(0,0,31)
+
+// Default UI Colors
+#define Def_Background_Color Color_Bg_Black
+#define Def_Cursor_color Rectangle_Color
+#define Def_TitleBg_color Color_Bg_Blue
+#define Def_TitleTxt_color Color_White
+#define Def_Text_Color Color_White
+#define Def_Selected_Color Select_Color
+#define Def_SplitLine_Color Line_Color
+#define Def_Highlight_Color Color_White
+#define Def_StatusBg_Color RGB(0,20,20)
+#define Def_StatusTxt_Color Color_Yellow
+#define Def_PopupBg_color Color_Bg_Window
+#define Def_PopupTxt_Color Popup_Text_Color
+#define Def_AlertBg_Color Color_Bg_Red
+#define Def_AlertTxt_Color Color_Yellow
+#define Def_PercentTxt_Color Percent_Color
+#define Def_Barfill_Color BarFill_Color
+#define Def_Indicator_Color Color_White
+#define Def_Coordinate_Color Color_White
+
+//UI elements defines and constants
+#define DWIN_FONT_MENU font8x16
+#define DWIN_FONT_STAT font10x20
+#define DWIN_FONT_HEAD font10x20
+#define DWIN_FONT_ALERT font10x20
+#define STATUS_Y 354
+#define LCD_WIDTH (DWIN_WIDTH / 8)
+
+constexpr uint16_t TITLE_HEIGHT = 30, // Title bar height
+ MLINE = 53, // Menu line height
+ TROWS = (STATUS_Y - TITLE_HEIGHT) / MLINE, // Total rows
+ MROWS = TROWS - 1, // Other-than-Back
+ ICOX = 26, // Menu item icon X position
+ LBLX = 60, // Menu item label X position
+ VALX = 210, // Menu item value X position
+ MENU_CHR_W = 8, MENU_CHR_H = 16, // Menu font 8x16
+ STAT_CHR_W = 10;
+
+// Menuitem Y position
+#define MYPOS(L) (TITLE_HEIGHT + MLINE * (L))
+
+// Menuitem caption Offset
+#define CAPOFF ((MLINE - MENU_CHR_H) / 2)
+
+// Menuitem caption Y position
+#define MBASE(L) (MYPOS(L) + CAPOFF)
+
+// Create and add a MenuItem object to the menu array
+#define ADDMENUITEM(V...) DWINUI::MenuItemsAdd(new MenuItemClass(V))
+#define ADDMENUITEM_P(V...) DWINUI::MenuItemsAdd(new MenuItemPtrClass(V))
+
+typedef struct { uint16_t left, top, right, bottom; } rect_t;
+typedef struct { uint16_t x, y, w, h; } frame_rect_t;
+
+class TitleClass {
+public:
+ char caption[32] = "";
+ uint8_t frameid = 0;
+ rect_t frame = {0};
+ void Draw();
+ void SetCaption(const char * const title);
+ inline void SetCaption(const __FlashStringHelper * title) { SetCaption((char *)title); }
+ void ShowCaption(const char * const title);
+ inline void ShowCaption(const __FlashStringHelper * title) { ShowCaption((char *)title); }
+ void SetFrame(uint8_t id, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
+ void SetFrame(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
+ void FrameCopy(uint8_t id, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
+ void FrameCopy(uint16_t x, uint16_t y, uint16_t h, uint16_t v);
+};
+extern TitleClass Title;
+
+class MenuItemClass {
+protected:
+public:
+ uint8_t pos = 0;
+ uint8_t icon = 0;
+ char caption[32] = "";
+ uint8_t frameid = 0;
+ rect_t frame = {0};
+ void (*onDraw)(MenuItemClass* menuitem, int8_t line) = nullptr;
+ void (*onClick)() = nullptr;
+ MenuItemClass() {};
+ MenuItemClass(uint8_t cicon, const char * const text=nullptr, void (*ondraw)(MenuItemClass* menuitem, int8_t line)=nullptr, void (*onclick)()=nullptr);
+ MenuItemClass(uint8_t cicon, const __FlashStringHelper * text = nullptr, void (*ondraw)(MenuItemClass* menuitem, int8_t line)=nullptr, void (*onclick)()=nullptr) : MenuItemClass(cicon, (char*)text, ondraw, onclick){}
+ MenuItemClass(uint8_t cicon, uint8_t id, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, void (*ondraw)(MenuItemClass* menuitem, int8_t line)=nullptr, void (*onclick)()=nullptr);
+ void SetFrame(uint8_t id, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
+ virtual ~MenuItemClass(){};
+ virtual void Draw(int8_t line);
+};
+
+class MenuItemPtrClass: public MenuItemClass {
+public:
+ void *value = nullptr;
+ using MenuItemClass::MenuItemClass;
+ MenuItemPtrClass(uint8_t cicon, const char * const text, void (*ondraw)(MenuItemClass* menuitem, int8_t line), void (*onclick)(), void* val);
+ MenuItemPtrClass(uint8_t cicon, const __FlashStringHelper * text, void (*ondraw)(MenuItemClass* menuitem, int8_t line), void (*onclick)(), void* val) : MenuItemPtrClass(cicon, (char*)text, ondraw, onclick, val){}
+};
+
+class MenuClass {
+public:
+ int8_t topline = 0;
+ int8_t selected = 0;
+ TitleClass MenuTitle;
+ MenuClass();
+ virtual ~MenuClass(){};
+ inline int8_t line() { return selected - topline; };
+ inline int8_t line(uint8_t pos) {return pos - topline; };
+ void Draw();
+ void onScroll(bool dir);
+ void onClick();
+ MenuItemClass* SelectedItem();
+};
+extern MenuClass *CurrentMenu;
+
+namespace DWINUI {
+ extern xy_int_t cursor;
+ extern uint16_t pencolor;
+ extern uint16_t textcolor;
+ extern uint16_t backcolor;
+ extern uint8_t font;
+
+ extern void (*onCursorErase)(uint8_t line);
+ extern void (*onCursorDraw)(uint8_t line);
+ extern void (*onTitleDraw)(TitleClass* title);
+ extern void (*onMenuDraw)(MenuClass* menu);
+
+ // DWIN LCD Initialization
+ void Init(void);
+
+ // Set text/number font
+ void SetFont(uint8_t cfont);
+
+ // Get font character width
+ uint8_t Get_font_width(uint8_t cfont);
+
+ // Get font character heigh
+ uint8_t Get_font_height(uint8_t cfont);
+
+ // Get screen x coodinates from text column
+ uint16_t ColToX(uint8_t col);
+
+ // Get screen y coodinates from text row
+ uint16_t RowToY(uint8_t row);
+
+ // Set text/number color
+ void SetColors(uint16_t fgcolor, uint16_t bgcolor);
+ void SetTextColor(uint16_t fgcolor);
+ void SetBackgroundColor(uint16_t bgcolor);
+
+ // Moves cursor to point
+ // x: abscissa of the display
+ // y: ordinate of the display
+ // point: xy coordinate
+ void MoveTo(int16_t x, int16_t y);
+ void MoveTo(xy_int_t point);
+
+ // Moves cursor relative to the actual position
+ // x: abscissa of the display
+ // y: ordinate of the display
+ // point: xy coordinate
+ void MoveBy(int16_t x, int16_t y);
+ void MoveBy(xy_int_t point);
+
+ // Draw a line from the cursor to xy position
+ // color: Line segment color
+ // x/y: End point
+ inline void LineTo(uint16_t color, uint16_t x, uint16_t y) {
+ DWIN_Draw_Line(color, cursor.x, cursor.y, x, y);
+ }
+ inline void LineTo(uint16_t x, uint16_t y) {
+ DWIN_Draw_Line(pencolor, cursor.x, cursor.y, x, y);
+ }
+
+ // Draw an Icon with transparent background from the library ICON
+ // icon: Icon ID
+ // x/y: Upper-left point
+ inline void Draw_Icon(uint8_t icon, uint16_t x, uint16_t y) {
+ DWIN_ICON_Show(ICON, icon, x, y);
+ }
+
+ // Draw a positive integer
+ // bShow: true=display background color; false=don't display background color
+ // zeroFill: true=zero fill; false=no zero fill
+ // zeroMode: 1=leading 0 displayed as 0; 0=leading 0 displayed as a space
+ // size: Font size
+ // color: Character color
+ // bColor: Background color
+ // iNum: Number of digits
+ // x/y: Upper-left coordinate
+ // value: Integer value
+ inline void Draw_Int(uint8_t bShow, bool zeroFill, uint8_t zeroMode, uint8_t size, uint16_t color, uint16_t bColor, uint8_t iNum, uint16_t x, uint16_t y, long value) {
+ DWIN_Draw_IntValue(bShow, zeroFill, zeroMode, size, color, bColor, iNum, x, y, value);
+ }
+ inline void Draw_Int(uint8_t iNum, long value) {
+ DWIN_Draw_IntValue(false, true, 0, font, textcolor, backcolor, iNum, cursor.x, cursor.y, value);
+ MoveBy(iNum * Get_font_width(font), 0);
+ }
+ inline void Draw_Int(uint8_t iNum, uint16_t x, uint16_t y, long value) {
+ DWIN_Draw_IntValue(false, true, 0, font, textcolor, backcolor, iNum, x, y, value);
+ }
+ inline void Draw_Int(uint16_t color, uint8_t iNum, uint16_t x, uint16_t y, long value) {
+ DWIN_Draw_IntValue(false, true, 0, font, color, backcolor, iNum, x, y, value);
+ }
+ inline void Draw_Int(uint16_t color, uint16_t bColor, uint8_t iNum, uint16_t x, uint16_t y, long value) {
+ DWIN_Draw_IntValue(true, true, 0, font, color, bColor, iNum, x, y, value);
+ }
+ inline void Draw_Int(uint8_t size, uint16_t color, uint16_t bColor, uint8_t iNum, uint16_t x, uint16_t y, long value) {
+ DWIN_Draw_IntValue(true, true, 0, size, color, bColor, iNum, x, y, value);
+ }
+
+ // Draw a floating point number
+ // bShow: true=display background color; false=don't display background color
+ // zeroFill: true=zero fill; false=no zero fill
+ // zeroMode: 1=leading 0 displayed as 0; 0=leading 0 displayed as a space
+ // size: Font size
+ // color: Character color
+ // bColor: Background color
+ // iNum: Number of whole digits
+ // fNum: Number of decimal digits
+ // x/y: Upper-left point
+ // value: Float value
+ inline void Draw_Float(uint8_t bShow, bool zeroFill, uint8_t zeroMode, uint8_t size, uint16_t color, uint16_t bColor, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value) {
+ DWIN_Draw_FloatValue(bShow, zeroFill, zeroMode, size, color, bColor, iNum, fNum, x, y, value);
+ }
+ inline void Draw_Float(uint8_t iNum, uint8_t fNum, float value) {
+ DWIN_Draw_FloatValue(false, true, 0, font, textcolor, backcolor, iNum, fNum, cursor.x, cursor.y, value);
+ MoveBy((iNum + fNum + 1) * Get_font_width(font), 0);
+ }
+ inline void Draw_Float(uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value) {
+ DWIN_Draw_FloatValue(false, true, 0, font, textcolor, backcolor, iNum, fNum, x, y, value);
+ }
+ inline void Draw_Float(uint16_t color, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value) {
+ DWIN_Draw_FloatValue(false, true, 0, font, color, backcolor, iNum, fNum, x, y, value);
+ }
+ inline void Draw_Float(uint16_t color, uint16_t bColor, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value) {
+ DWIN_Draw_FloatValue(true, true, 0, font, color, bColor, iNum, fNum, x, y, value);
+ }
+ inline void Draw_Float(uint8_t size, uint16_t color, uint16_t bColor, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value) {
+ DWIN_Draw_FloatValue(true, true, 0, size, color, bColor, iNum, fNum, x, y, value);
+ }
+
+ // Draw a signed floating point number
+ // bShow: true=display background color; false=don't display background color
+ // zeroFill: true=zero fill; false=no zero fill
+ // zeroMode: 1=leading 0 displayed as 0; 0=leading 0 displayed as a space
+ // size: Font size
+ // bColor: Background color
+ // iNum: Number of whole digits
+ // fNum: Number of decimal digits
+ // x/y: Upper-left point
+ // value: Float value
+ void Draw_Signed_Float(uint8_t bShow, bool zeroFill, uint8_t zeroMode, uint8_t size, uint16_t color, uint16_t bColor, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value);
+ inline void Draw_Signed_Float(uint8_t iNum, uint8_t fNum, float value) {
+ Draw_Signed_Float(false, true, 0, font, textcolor, backcolor, iNum, fNum, cursor.x, cursor.y, value);
+ MoveBy((iNum + fNum + 1) * Get_font_width(font), 0);
+ }
+ inline void Draw_Signed_Float(uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value) {
+ Draw_Signed_Float(false, true, 0, font, textcolor, backcolor, iNum, fNum, x, y, value);
+ }
+ inline void Draw_Signed_Float(uint8_t size, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value) {
+ Draw_Signed_Float(false, true, 0, size, textcolor, backcolor, iNum, fNum, x, y, value);
+ }
+ inline void Draw_Signed_Float(uint16_t color, uint16_t bColor, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value) {
+ Draw_Signed_Float(true, true, 0, font, color, bColor, iNum, fNum, x, y, value);
+ }
+ inline void Draw_Signed_Float(uint8_t size, uint16_t color, uint16_t bColor, uint8_t iNum, uint8_t fNum, uint16_t x, uint16_t y, float value) {
+ Draw_Signed_Float(true, true, 0, size, color, bColor, iNum, fNum, x, y, value);
+ }
+
+ // Draw a char at cursor position
+ void Draw_Char(const char c);
+
+ // Draw a string at cursor position
+ // color: Character color
+ // *string: The string
+ // rlimit: For draw less chars than string length use rlimit
+ void Draw_String(const char * const string, uint16_t rlimit = 0xFFFF);
+ void Draw_String(uint16_t color, const char * const string, uint16_t rlimit = 0xFFFF);
+
+ // Draw a string
+ // size: Font size
+ // color: Character color
+ // bColor: Background color
+ // x/y: Upper-left coordinate of the string
+ // *string: The string
+ inline void Draw_String(uint16_t x, uint16_t y, const char * const string) {
+ DWIN_Draw_String(false, font, textcolor, backcolor, x, y, string);
+ }
+ inline void Draw_String(uint16_t x, uint16_t y, const __FlashStringHelper *title) {
+ DWIN_Draw_String(false, font, textcolor, backcolor, x, y, (char *)title);
+ }
+ inline void Draw_String(uint16_t color, uint16_t x, uint16_t y, const char * const string) {
+ DWIN_Draw_String(false, font, color, backcolor, x, y, string);
+ }
+ inline void Draw_String(uint16_t color, uint16_t x, uint16_t y, const __FlashStringHelper *title) {
+ DWIN_Draw_String(false, font, color, backcolor, x, y, (char *)title);
+ }
+ inline void Draw_String(uint16_t color, uint16_t bgcolor, uint16_t x, uint16_t y, const char * const string) {
+ DWIN_Draw_String(true, font, color, bgcolor, x, y, string);
+ }
+ inline void Draw_String(uint16_t color, uint16_t bgcolor, uint16_t x, uint16_t y, const __FlashStringHelper *title) {
+ DWIN_Draw_String(true, font, color, bgcolor, x, y, (char *)title);
+ }
+ inline void Draw_String(uint8_t size, uint16_t color, uint16_t bgcolor, uint16_t x, uint16_t y, const char * const string) {
+ DWIN_Draw_String(true, size, color, bgcolor, x, y, string);
+ }
+ inline void Draw_String(uint8_t size, uint16_t color, uint16_t bgcolor, uint16_t x, uint16_t y, const __FlashStringHelper *title) {
+ DWIN_Draw_String(true, size, color, bgcolor, x, y, (char *)title);
+ }
+
+ // Draw a centered string using DWIN_WIDTH
+ // bShow: true=display background color; false=don't display background color
+ // size: Font size
+ // color: Character color
+ // bColor: Background color
+ // y: Upper coordinate of the string
+ // *string: The string
+ void Draw_CenteredString(bool bShow, uint8_t size, uint16_t color, uint16_t bColor, uint16_t y, const char * const string);
+ inline void Draw_CenteredString(bool bShow, uint8_t size, uint16_t color, uint16_t bColor, uint16_t y, const __FlashStringHelper *title) {
+ Draw_CenteredString(bShow, size, color, bColor, y, (char *)title);
+ }
+ inline void Draw_CenteredString(uint16_t color, uint16_t bcolor, uint16_t y, const char * const string) {
+ Draw_CenteredString(true, font, color, bcolor, y, string);
+ }
+ inline void Draw_CenteredString(uint8_t size, uint16_t color, uint16_t y, const char * const string) {
+ Draw_CenteredString(false, size, color, backcolor, y, string);
+ }
+ inline void Draw_CenteredString(uint8_t size, uint16_t color, uint16_t y, const __FlashStringHelper *title) {
+ Draw_CenteredString(false, size, color, backcolor, y, (char *)title);
+ }
+ inline void Draw_CenteredString(uint16_t color, uint16_t y, const char * const string) {
+ Draw_CenteredString(false, font, color, backcolor, y, string);
+ }
+ inline void Draw_CenteredString(uint16_t color, uint16_t y, const __FlashStringHelper *title) {
+ Draw_CenteredString(false, font, color, backcolor, y, (char *)title);
+ }
+ inline void Draw_CenteredString(uint16_t y, const char * const string) {
+ Draw_CenteredString(false, font, textcolor, backcolor, y, string);
+ }
+ inline void Draw_CenteredString(uint16_t y, const __FlashStringHelper *title) {
+ Draw_CenteredString(false, font, textcolor, backcolor, y, (char *)title);
+ }
+
+ // Draw a circle
+ // Color: circle color
+ // x: the abscissa of the center of the circle
+ // y: ordinate of the center of the circle
+ // r: circle radius
+ void Draw_Circle(uint16_t color, uint16_t x,uint16_t y,uint8_t r);
+ inline void Draw_Circle(uint16_t color, uint8_t r) {
+ Draw_Circle(color, cursor.x, cursor.y, r);
+ }
+
+ // Draw a checkbox
+ // Color: frame color
+ // bColor: Background color
+ // x/y: Upper-left point
+ // checked : 0 : unchecked, 1 : checked
+ void Draw_Checkbox(uint16_t color, uint16_t bcolor, uint16_t x, uint16_t y, bool checked);
+ inline void Draw_Checkbox(uint16_t x, uint16_t y, bool checked=false) {
+ Draw_Checkbox(textcolor, backcolor, x, y, checked);
+ }
+
+ // Color Interpolator
+ // val : Interpolator minv..maxv
+ // minv : Minimum value
+ // maxv : Maximun value
+ // color1 : Start color
+ // color2 : End color
+ uint16_t ColorInt(int16_t val, int16_t minv, int16_t maxv, uint16_t color1, uint16_t color2);
+
+ // -------------------------- Extra -------------------------------//
+
+ // Draw a circle filled with color
+ // bcolor: fill color
+ // x: the abscissa of the center of the circle
+ // y: ordinate of the center of the circle
+ // r: circle radius
+ void Draw_FillCircle(uint16_t bcolor, uint16_t x,uint16_t y,uint8_t r);
+ inline void Draw_FillCircle(uint16_t bcolor, uint8_t r) {
+ Draw_FillCircle(bcolor, cursor.x, cursor.y, r);
+ }
+
+ // Color Interpolator through Red->Yellow->Green->Blue
+ // val : Interpolator minv..maxv
+ // minv : Minimum value
+ // maxv : Maximun value
+ uint16_t RainbowInt(int16_t val, int16_t minv, int16_t maxv);
+
+ // Write buffer data to the SRAM
+ // addr: SRAM start address 0x0000-0x7FFF
+ // length: Bytes to write
+ // data: address of the buffer with data
+ inline void WriteToSRAM(uint16_t addr, uint16_t length, uint8_t *data) {
+ DWIN_WriteToMem(0x5A, addr, length, data);
+ }
+
+ // Write buffer data to the Flash
+ // addr: Flash start address 0x0000-0x3FFF
+ // length: Bytes to write
+ // data: address of the buffer with data
+ inline void WriteToFlash(uint16_t addr, uint16_t length, uint8_t *data) {
+ DWIN_WriteToMem(0xA5, addr, length, data);
+ }
+
+ // Clear Menu by filling the area with background color
+ // Area (0, TITLE_HEIGHT, DWIN_WIDTH, STATUS_Y - 1)
+ void ClearMenuArea();
+
+ // Clear MenuItems array and free MenuItems elements
+ void MenuItemsClear();
+
+ // Prepare MenuItems array
+ void MenuItemsPrepare(uint8_t totalitems);
+
+ // Add elements to the MenuItems array
+ MenuItemClass* MenuItemsAdd(MenuItemClass* menuitem);
+
+};
diff --git a/Marlin/src/lcd/e3v2/enhanced/lockscreen.cpp b/Marlin/src/lcd/e3v2/enhanced/lockscreen.cpp
new file mode 100644
index 0000000000..2615a05881
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/enhanced/lockscreen.cpp
@@ -0,0 +1,69 @@
+/**
+ * DWIN UI Enhanced implementation
+ * Author: Miguel A. Risco-Castillo
+ * Version: 3.6.1
+ * Date: 2021/08/29
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser 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 Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+
+#include "../../../core/types.h"
+#include "dwin_lcd.h"
+#include "dwinui.h"
+#include "dwin.h"
+#include "lockscreen.h"
+
+LockScreenClass LockScreen;
+
+void LockScreenClass::Init() {
+ Lock_Pos = 0;
+ unlocked = false;
+ Draw();
+}
+
+void LockScreenClass::Draw() {
+ Title.SetCaption(PSTR("Lock Screen"));
+ DWINUI::ClearMenuArea();
+ DWINUI::Draw_Icon(ICON_LOGO, 71, 120); // CREALITY logo
+ DWINUI::Draw_CenteredString(Color_White, 180, F("Printer is Locked,"));
+ DWINUI::Draw_CenteredString(Color_White, 200, F("Scroll to unlock."));
+ DWINUI::Draw_CenteredString(Color_White, 240, F("-> | <-"));
+ DWIN_Draw_Box(1, HMI_data.Barfill_Color, 0, 260, DWIN_WIDTH, 20);
+ DWIN_Draw_VLine(Color_Yellow, Lock_Pos * DWIN_WIDTH / 255, 260, 20);
+ DWIN_UpdateLCD();
+}
+
+void LockScreenClass::onEncoderState(ENCODER_DiffState encoder_diffState) {
+ if (encoder_diffState == ENCODER_DIFF_CW) {
+ Lock_Pos += 8;
+ }
+ else if (encoder_diffState == ENCODER_DIFF_CCW) {
+ Lock_Pos -= 8;
+ }
+ else if (encoder_diffState == ENCODER_DIFF_ENTER) {
+ unlocked = (Lock_Pos == 128);
+ }
+ DWIN_Draw_Box(1, HMI_data.Barfill_Color, 0, 260, DWIN_WIDTH, 20);
+ DWIN_Draw_VLine(Color_Yellow, Lock_Pos * DWIN_WIDTH / 255, 260, 20);
+ DWIN_UpdateLCD();
+}
+
+bool LockScreenClass::isUnlocked() { return unlocked; }
+
+#endif // DWIN_CREALITY_LCD_ENHANCED
diff --git a/Marlin/src/lcd/e3v2/enhanced/lockscreen.h b/Marlin/src/lcd/e3v2/enhanced/lockscreen.h
new file mode 100644
index 0000000000..32a0cc3e5a
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/enhanced/lockscreen.h
@@ -0,0 +1,35 @@
+/**
+ * DWIN UI Enhanced implementation
+ * Author: Miguel A. Risco-Castillo
+ * Version: 3.6.1
+ * Date: 2021/08/29
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser 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 Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+#pragma once
+
+#include "../../../core/types.h"
+
+class LockScreenClass {
+private:
+ uint8_t Lock_Pos = 0;
+ bool unlocked = false;
+public:
+ void Init();
+ void onEncoderState(ENCODER_DiffState encoder_diffState);
+ void Draw();
+ bool isUnlocked();
+};
+extern LockScreenClass LockScreen;
diff --git a/Marlin/src/lcd/e3v2/enhanced/rotary_encoder.cpp b/Marlin/src/lcd/e3v2/enhanced/rotary_encoder.cpp
new file mode 100644
index 0000000000..4f815fdee0
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/enhanced/rotary_encoder.cpp
@@ -0,0 +1,261 @@
+/**
+ * DWIN UI Enhanced implementation
+ * Author: Miguel A. Risco-Castillo
+ * Version: 3.6.1
+ * Date: 2021/08/29
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser 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 Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/*****************************************************************************
+ * @file lcd/e3v2/enhanced/rotary_encoder.cpp
+ * @author LEO / Creality3D
+ * @date 2019/07/06
+ * @version 2.0.1
+ * @brief Rotary encoder functions
+ *****************************************************************************/
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+
+#include "rotary_encoder.h"
+#include "../../buttons.h"
+
+#include "../../../MarlinCore.h"
+#include "../../../HAL/shared/Delay.h"
+
+#if HAS_BUZZER
+ #include "../../../libs/buzzer.h"
+#endif
+
+#include
+
+#ifndef ENCODER_PULSES_PER_STEP
+ #define ENCODER_PULSES_PER_STEP 4
+#endif
+
+#if ENABLED(SOUND_MENU_ITEM)
+ #include "../../marlinui.h"
+#endif
+
+ENCODER_Rate EncoderRate;
+
+// Buzzer
+void Encoder_tick() {
+ #if PIN_EXISTS(BEEPER)
+ if (TERN1(SOUND_MENU_ITEM, ui.buzzer_enabled)) {
+ WRITE(BEEPER_PIN, HIGH);
+ delay(10);
+ WRITE(BEEPER_PIN, LOW);
+ }
+ #endif
+}
+
+// Encoder initialization
+void Encoder_Configuration() {
+ #if BUTTON_EXISTS(EN1)
+ SET_INPUT_PULLUP(BTN_EN1);
+ #endif
+ #if BUTTON_EXISTS(EN2)
+ SET_INPUT_PULLUP(BTN_EN2);
+ #endif
+ #if BUTTON_EXISTS(ENC)
+ SET_INPUT_PULLUP(BTN_ENC);
+ #endif
+ #if PIN_EXISTS(BEEPER)
+ SET_OUTPUT(BEEPER_PIN);
+ #endif
+}
+
+// Analyze encoder value and return state
+ENCODER_DiffState Encoder_ReceiveAnalyze() {
+ const millis_t now = millis();
+ static uint8_t lastEncoderBits;
+ uint8_t newbutton = 0;
+ static signed char temp_diff = 0;
+
+ ENCODER_DiffState temp_diffState = ENCODER_DIFF_NO;
+ if (BUTTON_PRESSED(EN1)) newbutton |= EN_A;
+ if (BUTTON_PRESSED(EN2)) newbutton |= EN_B;
+ if (BUTTON_PRESSED(ENC)) {
+ static millis_t next_click_update_ms;
+ if (ELAPSED(now, next_click_update_ms)) {
+ next_click_update_ms = millis() + 300;
+ Encoder_tick();
+ #if PIN_EXISTS(LCD_LED)
+ //LED_Action();
+ #endif
+ const bool was_waiting = wait_for_user;
+ wait_for_user = false;
+ return was_waiting ? ENCODER_DIFF_NO : ENCODER_DIFF_ENTER;
+ }
+ else return ENCODER_DIFF_NO;
+ }
+ if (newbutton != lastEncoderBits) {
+ switch (newbutton) {
+ case ENCODER_PHASE_0:
+ if (lastEncoderBits == ENCODER_PHASE_3) temp_diff++;
+ else if (lastEncoderBits == ENCODER_PHASE_1) temp_diff--;
+ break;
+ case ENCODER_PHASE_1:
+ if (lastEncoderBits == ENCODER_PHASE_0) temp_diff++;
+ else if (lastEncoderBits == ENCODER_PHASE_2) temp_diff--;
+ break;
+ case ENCODER_PHASE_2:
+ if (lastEncoderBits == ENCODER_PHASE_1) temp_diff++;
+ else if (lastEncoderBits == ENCODER_PHASE_3) temp_diff--;
+ break;
+ case ENCODER_PHASE_3:
+ if (lastEncoderBits == ENCODER_PHASE_2) temp_diff++;
+ else if (lastEncoderBits == ENCODER_PHASE_0) temp_diff--;
+ break;
+ }
+ lastEncoderBits = newbutton;
+ }
+
+ if (ABS(temp_diff) >= ENCODER_PULSES_PER_STEP) {
+ if (temp_diff > 0) temp_diffState = ENCODER_DIFF_CW;
+ else temp_diffState = ENCODER_DIFF_CCW;
+
+ #if ENABLED(ENCODER_RATE_MULTIPLIER)
+
+ millis_t ms = millis();
+ int32_t encoderMultiplier = 1;
+
+ // if must encoder rati multiplier
+ if (EncoderRate.enabled) {
+ const float abs_diff = ABS(temp_diff),
+ encoderMovementSteps = abs_diff / (ENCODER_PULSES_PER_STEP);
+ if (EncoderRate.lastEncoderTime) {
+ // Note that the rate is always calculated between two passes through the
+ // loop and that the abs of the temp_diff value is tracked.
+ const float encoderStepRate = encoderMovementSteps / float(ms - EncoderRate.lastEncoderTime) * 1000;
+ if (encoderStepRate >= ENCODER_100X_STEPS_PER_SEC) encoderMultiplier = 100;
+ else if (encoderStepRate >= ENCODER_10X_STEPS_PER_SEC) encoderMultiplier = 10;
+ else if (encoderStepRate >= ENCODER_5X_STEPS_PER_SEC) encoderMultiplier = 5;
+ }
+ EncoderRate.lastEncoderTime = ms;
+ }
+
+ #else
+
+ constexpr int32_t encoderMultiplier = 1;
+
+ #endif
+
+ // EncoderRate.encoderMoveValue += (temp_diff * encoderMultiplier) / (ENCODER_PULSES_PER_STEP);
+ EncoderRate.encoderMoveValue = (temp_diff * encoderMultiplier) / (ENCODER_PULSES_PER_STEP);
+ if (EncoderRate.encoderMoveValue < 0) EncoderRate.encoderMoveValue = -EncoderRate.encoderMoveValue;
+
+ temp_diff = 0;
+ }
+ return temp_diffState;
+}
+
+#if PIN_EXISTS(LCD_LED)
+
+ // Take the low 24 valid bits 24Bit: G7 G6 G5 G4 G3 G2 G1 G0 R7 R6 R5 R4 R3 R2 R1 R0 B7 B6 B5 B4 B3 B2 B1 B0
+ uint16_t LED_DataArray[LED_NUM];
+
+ // LED light operation
+ void LED_Action() {
+ LED_Control(RGB_SCALE_WARM_WHITE,0x0F);
+ delay(30);
+ LED_Control(RGB_SCALE_WARM_WHITE,0x00);
+ }
+
+ // LED initialization
+ void LED_Configuration() {
+ SET_OUTPUT(LCD_LED_PIN);
+ }
+
+ // LED write data
+ void LED_WriteData() {
+ uint8_t tempCounter_LED, tempCounter_Bit;
+ for (tempCounter_LED = 0; tempCounter_LED < LED_NUM; tempCounter_LED++) {
+ for (tempCounter_Bit = 0; tempCounter_Bit < 24; tempCounter_Bit++) {
+ if (LED_DataArray[tempCounter_LED] & (0x800000 >> tempCounter_Bit)) {
+ LED_DATA_HIGH;
+ DELAY_NS(300);
+ LED_DATA_LOW;
+ DELAY_NS(200);
+ }
+ else {
+ LED_DATA_HIGH;
+ LED_DATA_LOW;
+ DELAY_NS(200);
+ }
+ }
+ }
+ }
+
+ // LED control
+ // RGB_Scale: RGB color ratio
+ // luminance: brightness (0~0xFF)
+ void LED_Control(const uint8_t RGB_Scale, const uint8_t luminance) {
+ for (uint8_t i = 0; i < LED_NUM; i++) {
+ LED_DataArray[i] = 0;
+ switch (RGB_Scale) {
+ case RGB_SCALE_R10_G7_B5: LED_DataArray[i] = (luminance * 10/10) << 8 | (luminance * 7/10) << 16 | luminance * 5/10; break;
+ case RGB_SCALE_R10_G7_B4: LED_DataArray[i] = (luminance * 10/10) << 8 | (luminance * 7/10) << 16 | luminance * 4/10; break;
+ case RGB_SCALE_R10_G8_B7: LED_DataArray[i] = (luminance * 10/10) << 8 | (luminance * 8/10) << 16 | luminance * 7/10; break;
+ }
+ }
+ LED_WriteData();
+ }
+
+ // LED gradient control
+ // RGB_Scale: RGB color ratio
+ // luminance: brightness (0~0xFF)
+ // change_Time: gradient time (ms)
+ void LED_GraduallyControl(const uint8_t RGB_Scale, const uint8_t luminance, const uint16_t change_Interval) {
+ struct { uint8_t g, r, b; } led_data[LED_NUM];
+ for (uint8_t i = 0; i < LED_NUM; i++) {
+ switch (RGB_Scale) {
+ case RGB_SCALE_R10_G7_B5:
+ led_data[i] = { luminance * 7/10, luminance * 10/10, luminance * 5/10 };
+ break;
+ case RGB_SCALE_R10_G7_B4:
+ led_data[i] = { luminance * 7/10, luminance * 10/10, luminance * 4/10 };
+ break;
+ case RGB_SCALE_R10_G8_B7:
+ led_data[i] = { luminance * 8/10, luminance * 10/10, luminance * 7/10 };
+ break;
+ }
+ }
+
+ struct { bool g, r, b; } led_flag = { false, false, false };
+ for (uint8_t i = 0; i < LED_NUM; i++) {
+ while (1) {
+ const uint8_t g = uint8_t(LED_DataArray[i] >> 16),
+ r = uint8_t(LED_DataArray[i] >> 8),
+ b = uint8_t(LED_DataArray[i]);
+ if (g == led_data[i].g) led_flag.g = true;
+ else LED_DataArray[i] += (g > led_data[i].g) ? -0x010000 : 0x010000;
+ if (r == led_data[i].r) led_flag.r = true;
+ else LED_DataArray[i] += (r > led_data[i].r) ? -0x000100 : 0x000100;
+ if (b == led_data[i].b) led_flag.b = true;
+ else LED_DataArray[i] += (b > led_data[i].b) ? -0x000001 : 0x000001;
+ LED_WriteData();
+ if (led_flag.r && led_flag.g && led_flag.b) break;
+ delay(change_Interval);
+ }
+ }
+ }
+
+#endif // LCD_LED
+
+#endif // DWIN_CREALITY_LCD_ENHANCED
diff --git a/Marlin/src/lcd/e3v2/enhanced/rotary_encoder.h b/Marlin/src/lcd/e3v2/enhanced/rotary_encoder.h
new file mode 100644
index 0000000000..c500cfe5bb
--- /dev/null
+++ b/Marlin/src/lcd/e3v2/enhanced/rotary_encoder.h
@@ -0,0 +1,93 @@
+/**
+ * DWIN UI Enhanced implementation
+ * Author: Miguel A. Risco-Castillo
+ * Version: 3.6.1
+ * Date: 2021/08/29
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser 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 Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+#pragma once
+
+/*****************************************************************************
+ * @file lcd/e3v2/enhanced/rotary_encoder.h
+ * @author LEO / Creality3D
+ * @date 2019/07/06
+ * @version 2.0.1
+ * @brief Rotary encoder functions
+ ****************************************************************************/
+
+#include "../../../inc/MarlinConfig.h"
+
+/*********************** Encoder Set ***********************/
+
+typedef struct {
+ bool enabled = false;
+ int encoderMoveValue = 0;
+ millis_t lastEncoderTime = 0;
+} ENCODER_Rate;
+
+extern ENCODER_Rate EncoderRate;
+
+typedef enum {
+ ENCODER_DIFF_NO = 0, // no state
+ ENCODER_DIFF_CW = 1, // clockwise rotation
+ ENCODER_DIFF_CCW = 2, // counterclockwise rotation
+ ENCODER_DIFF_ENTER = 3 // click
+} ENCODER_DiffState;
+
+// Encoder initialization
+void Encoder_Configuration();
+
+// Analyze encoder value and return state
+ENCODER_DiffState Encoder_ReceiveAnalyze();
+
+/*********************** Encoder LED ***********************/
+
+#if PIN_EXISTS(LCD_LED)
+
+ #define LED_NUM 4
+ #define LED_DATA_HIGH WRITE(LCD_LED_PIN, 1)
+ #define LED_DATA_LOW WRITE(LCD_LED_PIN, 0)
+
+ #define RGB_SCALE_R10_G7_B5 1
+ #define RGB_SCALE_R10_G7_B4 2
+ #define RGB_SCALE_R10_G8_B7 3
+ #define RGB_SCALE_NEUTRAL_WHITE RGB_SCALE_R10_G7_B5
+ #define RGB_SCALE_WARM_WHITE RGB_SCALE_R10_G7_B4
+ #define RGB_SCALE_COOL_WHITE RGB_SCALE_R10_G8_B7
+
+ extern unsigned int LED_DataArray[LED_NUM];
+
+ // LED light operation
+ void LED_Action();
+
+ // LED initialization
+ void LED_Configuration();
+
+ // LED write data
+ void LED_WriteData();
+
+ // LED control
+ // RGB_Scale: RGB color ratio
+ // luminance: brightness (0~0xFF)
+ void LED_Control(const uint8_t RGB_Scale, const uint8_t luminance);
+
+ // LED gradient control
+ // RGB_Scale: RGB color ratio
+ // luminance: brightness (0~0xFF)
+ // change_Time: gradient time (ms)
+ void LED_GraduallyControl(const uint8_t RGB_Scale, const uint8_t luminance, const uint16_t change_Interval);
+
+#endif // LCD_LED
diff --git a/Marlin/src/lcd/e3v2/jyersui/dwin_lcd.h b/Marlin/src/lcd/e3v2/jyersui/dwin_lcd.h
index 18b7c34744..9f8bd25295 100644
--- a/Marlin/src/lcd/e3v2/jyersui/dwin_lcd.h
+++ b/Marlin/src/lcd/e3v2/jyersui/dwin_lcd.h
@@ -44,9 +44,6 @@
// Handshake (1: Success, 0: Fail)
bool DWIN_Handshake(void);
-// Common DWIN startup
-void DWIN_Startup(void);
-
// Set the backlight luminance
// luminance: (0x00-0xFF)
void DWIN_Backlight_SetLuminance(const uint8_t luminance);
diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h
index bddddb75bf..11c976ee41 100644
--- a/Marlin/src/lcd/language/language_en.h
+++ b/Marlin/src/lcd/language/language_en.h
@@ -78,6 +78,14 @@ namespace Language_en {
PROGMEM Language_Str MSG_AUTO_HOME_I = _UxGT("Home ") LCD_STR_I;
PROGMEM Language_Str MSG_AUTO_HOME_J = _UxGT("Home ") LCD_STR_J;
PROGMEM Language_Str MSG_AUTO_HOME_K = _UxGT("Home ") LCD_STR_K;
+ PROGMEM Language_Str MSG_FILAMENT_SET = _UxGT("Filament Settings");
+ PROGMEM Language_Str MSG_FILAMENT_MAN = _UxGT("Filament Management");
+ PROGMEM Language_Str MSG_LEVBED_FL = _UxGT("Front Left");
+ PROGMEM Language_Str MSG_LEVBED_FR = _UxGT("Front Right");
+ PROGMEM Language_Str MSG_LEVBED_C = _UxGT("Center");
+ PROGMEM Language_Str MSG_LEVBED_BL = _UxGT("Back Left");
+ PROGMEM Language_Str MSG_LEVBED_BR = _UxGT("Back Right");
+ PROGMEM Language_Str MSG_MANUAL_MESH = _UxGT("Manual Mesh");
PROGMEM Language_Str MSG_AUTO_Z_ALIGN = _UxGT("Auto Z-Align");
PROGMEM Language_Str MSG_ITERATION = _UxGT("G34 Iteration: %i");
PROGMEM Language_Str MSG_DECREASING_ACCURACY = _UxGT("Accuracy Decreasing!");
@@ -289,6 +297,11 @@ namespace Language_en {
PROGMEM Language_Str MSG_MOVE_01IN = _UxGT("Move 0.1in");
PROGMEM Language_Str MSG_MOVE_1IN = _UxGT("Move 1.0in");
PROGMEM Language_Str MSG_SPEED = _UxGT("Speed");
+ PROGMEM Language_Str MSG_MAXSPEED = _UxGT("Max Speed (mm/s)");
+ PROGMEM Language_Str MSG_MAXSPEED_X = _UxGT("Max ") LCD_STR_A _UxGT(" Speed");
+ PROGMEM Language_Str MSG_MAXSPEED_Y = _UxGT("Max ") LCD_STR_B _UxGT(" Speed");
+ PROGMEM Language_Str MSG_MAXSPEED_Z = _UxGT("Max ") LCD_STR_C _UxGT(" Speed");
+ PROGMEM Language_Str MSG_MAXSPEED_E = _UxGT("Max ") LCD_STR_E _UxGT(" Speed");
PROGMEM Language_Str MSG_BED_Z = _UxGT("Bed Z");
PROGMEM Language_Str MSG_NOZZLE = _UxGT("Nozzle");
PROGMEM Language_Str MSG_NOZZLE_N = _UxGT("Nozzle ~");
@@ -321,7 +334,7 @@ namespace Language_en {
PROGMEM Language_Str MSG_LCD_OFF = _UxGT("Off");
PROGMEM Language_Str MSG_PID_AUTOTUNE = _UxGT("PID Autotune");
PROGMEM Language_Str MSG_PID_AUTOTUNE_E = _UxGT("PID Autotune *");
- PROGMEM Language_Str MSG_PID_CYCLE = _UxGT("PID Cycle");
+ PROGMEM Language_Str MSG_PID_CYCLE = _UxGT("PID Cycles");
PROGMEM Language_Str MSG_PID_AUTOTUNE_DONE = _UxGT("PID tuning done");
PROGMEM Language_Str MSG_PID_BAD_EXTRUDER_NUM = _UxGT("Autotune failed. Bad extruder.");
PROGMEM Language_Str MSG_PID_TEMP_TOO_HIGH = _UxGT("Autotune failed. Temperature too high.");
@@ -504,6 +517,7 @@ namespace Language_en {
PROGMEM Language_Str MSG_MANUAL_DEPLOY = _UxGT("Deploy Z-Probe");
PROGMEM Language_Str MSG_MANUAL_STOW = _UxGT("Stow Z-Probe");
PROGMEM Language_Str MSG_HOME_FIRST = _UxGT("Home %s%s%s First");
+ PROGMEM Language_Str MSG_ZPROBE_SETTINGS = _UxGT("Probe Settings");
PROGMEM Language_Str MSG_ZPROBE_OFFSETS = _UxGT("Probe Offsets");
PROGMEM Language_Str MSG_ZPROBE_XOFFSET = _UxGT("Probe X Offset");
PROGMEM Language_Str MSG_ZPROBE_YOFFSET = _UxGT("Probe Y Offset");
@@ -611,6 +625,7 @@ namespace Language_en {
PROGMEM Language_Str MSG_FILAMENT_CHANGE_NOZZLE = _UxGT(" Nozzle: ");
PROGMEM Language_Str MSG_RUNOUT_SENSOR = _UxGT("Runout Sensor");
PROGMEM Language_Str MSG_RUNOUT_DISTANCE_MM = _UxGT("Runout Dist mm");
+ PROGMEM Language_Str MSG_RUNOUT_ENABLE = _UxGT("Enable Runout");
PROGMEM Language_Str MSG_KILL_HOMING_FAILED = _UxGT("Homing Failed");
PROGMEM Language_Str MSG_LCD_PROBING_FAILED = _UxGT("Probing Failed");
diff --git a/Marlin/src/lcd/marlinui.cpp b/Marlin/src/lcd/marlinui.cpp
index 51cbf4534f..8c59500574 100644
--- a/Marlin/src/lcd/marlinui.cpp
+++ b/Marlin/src/lcd/marlinui.cpp
@@ -48,6 +48,8 @@ MarlinUI ui;
#if ENABLED(DWIN_CREALITY_LCD)
#include "e3v2/creality/dwin.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "e3v2/enhanced/dwin.h"
#elif ENABLED(DWIN_CREALITY_LCD_JYERSUI)
#include "e3v2/jyersui/dwin.h"
#endif
@@ -101,6 +103,7 @@ constexpr uint8_t epps = ENCODER_PULSES_PER_STEP;
backlight = !!value;
if (backlight) brightness = constrain(value, MIN_LCD_BRIGHTNESS, MAX_LCD_BRIGHTNESS);
// Set brightness on enabled LCD here
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_LCD_Brightness(brightness));
TERN_(DWIN_CREALITY_LCD_JYERSUI, DWIN_Backlight_SetLuminance(backlight ? brightness : 0));
}
#endif
@@ -1474,7 +1477,7 @@ constexpr uint8_t epps = ENCODER_PULSES_PER_STEP;
#endif
TERN_(EXTENSIBLE_UI, ExtUI::onStatusChanged(status_message));
- TERN_(DWIN_CREALITY_LCD, DWIN_StatusChanged(status_message));
+ TERN_(HAS_DWIN_E3V2_BASIC, DWIN_StatusChanged(status_message));
TERN_(DWIN_CREALITY_LCD_JYERSUI, CrealityDWIN.Update_Status(status_message));
}
diff --git a/Marlin/src/lcd/marlinui.h b/Marlin/src/lcd/marlinui.h
index 268d018508..d3a3c9d521 100644
--- a/Marlin/src/lcd/marlinui.h
+++ b/Marlin/src/lcd/marlinui.h
@@ -55,11 +55,17 @@
#include "../module/printcounter.h"
#endif
-#if ENABLED(ADVANCED_PAUSE_FEATURE) && ANY(HAS_LCD_MENU, EXTENSIBLE_UI, DWIN_CREALITY_LCD_JYERSUI)
+#if ENABLED(ADVANCED_PAUSE_FEATURE) && ANY(HAS_LCD_MENU, EXTENSIBLE_UI, HAS_DWIN_E3V2)
#include "../feature/pause.h"
#include "../module/motion.h" // for active_extruder
#endif
+#if ENABLED(DWIN_CREALITY_LCD)
+ #include "e3v2/creality/dwin.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "e3v2/enhanced/dwin.h"
+#endif
+
#define START_OF_UTF8_CHAR(C) (((C) & 0xC0u) != 0x80U)
#if HAS_WIRED_LCD
@@ -257,7 +263,7 @@ public:
FORCE_INLINE static void refresh_brightness() { set_brightness(brightness); }
#endif
- #if ENABLED(DWIN_CREALITY_LCD)
+ #if HAS_DWIN_E3V2_BASIC
static void refresh();
#else
FORCE_INLINE static void refresh() {
@@ -315,7 +321,7 @@ public:
#if HAS_STATUS_MESSAGE
- #if HAS_WIRED_LCD
+ #if EITHER(HAS_WIRED_LCD, DWIN_CREALITY_LCD_ENHANCED)
#if ENABLED(STATUS_MESSAGE_SCROLLING)
#define MAX_MESSAGE_LENGTH _MAX(LONG_FILENAME_LENGTH, MAX_LANG_CHARSIZE * 2 * (LCD_WIDTH))
#else
@@ -351,6 +357,12 @@ public:
static inline void reset_alert_level() {}
#endif
+ #if EITHER(HAS_DISPLAY, DWIN_CREALITY_LCD_ENHANCED)
+ static void kill_screen(PGM_P const lcd_error, PGM_P const lcd_component);
+ #else
+ static inline void kill_screen(PGM_P const, PGM_P const) {}
+ #endif
+
#if HAS_DISPLAY
static void init();
@@ -457,7 +469,6 @@ public:
static bool did_first_redraw;
#endif
- static void kill_screen(PGM_P const lcd_error, PGM_P const lcd_component);
static void draw_kill_screen();
#else // No LCD
@@ -585,7 +596,7 @@ public:
static inline bool use_click() { return false; }
#endif
- #if ENABLED(ADVANCED_PAUSE_FEATURE) && ANY(HAS_LCD_MENU, EXTENSIBLE_UI, DWIN_CREALITY_LCD_JYERSUI)
+ #if ENABLED(ADVANCED_PAUSE_FEATURE) && ANY(HAS_LCD_MENU, EXTENSIBLE_UI, HAS_DWIN_E3V2)
static void pause_show_message(const PauseMessage message, const PauseMode mode=PAUSE_MODE_SAME, const uint8_t extruder=active_extruder);
#else
static inline void _pause_show_message() {}
diff --git a/Marlin/src/lcd/tft/ui_common.h b/Marlin/src/lcd/tft/ui_common.h
index 617447a181..759712b64c 100644
--- a/Marlin/src/lcd/tft/ui_common.h
+++ b/Marlin/src/lcd/tft/ui_common.h
@@ -23,7 +23,7 @@
#include "../../inc/MarlinConfigPre.h"
-#if !HAS_LCD_MENU
+#if ENABLED(NO_LCD_MENUS)
#error "Seriously? High resolution TFT screen without menu?"
#endif
diff --git a/Marlin/src/module/probe.cpp b/Marlin/src/module/probe.cpp
index cbadda2eef..a4469bb209 100644
--- a/Marlin/src/module/probe.cpp
+++ b/Marlin/src/module/probe.cpp
@@ -79,6 +79,8 @@
#if ENABLED(EXTENSIBLE_UI)
#include "../lcd/extui/ui_api.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../lcd/e3v2/enhanced/dwin.h"
#endif
#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
@@ -295,7 +297,7 @@ FORCE_INLINE void probe_specific_action(const bool deploy) {
TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, PSTR("Stow Probe"), CONTINUE_STR));
TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(PSTR("Stow Probe")));
-
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_Popup_Confirm(ICON_BLTouch, PSTR("Stow Probe"), CONTINUE_STR));
wait_for_user_response();
ui.reset_status();
diff --git a/Marlin/src/module/settings.cpp b/Marlin/src/module/settings.cpp
index f2d2aeee92..c24a63ee51 100644
--- a/Marlin/src/module/settings.cpp
+++ b/Marlin/src/module/settings.cpp
@@ -71,9 +71,9 @@
#if ENABLED(EXTENSIBLE_UI)
#include "../lcd/extui/ui_api.h"
-#endif
-
-#if ENABLED(DWIN_CREALITY_LCD_JYERSUI)
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../lcd/e3v2/enhanced/dwin.h"
+#elif ENABLED(DWIN_CREALITY_LCD_JYERSUI)
#include "../lcd/e3v2/jyersui/dwin.h"
#endif
@@ -441,14 +441,15 @@ typedef struct SettingsDataStruct {
// EXTENSIBLE_UI
//
#if ENABLED(EXTENSIBLE_UI)
- // This is a significant hardware change; don't reserve space when not present
uint8_t extui_data[ExtUI::eeprom_data_size];
#endif
//
- // DWIN_CREALITY_LCD_JYERSUI
+ // Ender-3 V2 DWIN
//
- #if ENABLED(DWIN_CREALITY_LCD_JYERSUI)
+ #if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ uint8_t dwin_data[eeprom_data_size];
+ #elif ENABLED(DWIN_CREALITY_LCD_JYERSUI)
uint8_t dwin_settings[CrealityDWIN.eeprom_data_size];
#endif
@@ -1358,9 +1359,16 @@ void MarlinSettings::postprocess() {
#endif
//
- // Creality UI Settings
+ // Creality DWIN User Data
//
- #if ENABLED(DWIN_CREALITY_LCD_JYERSUI)
+ #if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ {
+ char dwin_data[eeprom_data_size] = { 0 };
+ DWIN_StoreSettings(dwin_data);
+ _FIELD_TEST(dwin_data);
+ EEPROM_WRITE(dwin_data);
+ }
+ #elif ENABLED(DWIN_CREALITY_LCD_JYERSUI)
{
char dwin_settings[CrealityDWIN.eeprom_data_size] = { 0 };
CrealityDWIN.Save_Settings(dwin_settings);
@@ -1488,6 +1496,8 @@ void MarlinSettings::postprocess() {
stored_ver[1] = '\0';
}
DEBUG_ECHO_MSG("EEPROM version mismatch (EEPROM=", stored_ver, " Marlin=" EEPROM_VERSION ")");
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, ui.set_status(GET_TEXT(MSG_ERR_EEPROM_VERSION)));
+
IF_DISABLED(EEPROM_AUTO_INIT, ui.eeprom_alert_version());
eeprom_error = true;
}
@@ -2249,9 +2259,16 @@ void MarlinSettings::postprocess() {
#endif
//
- // Creality UI Settings
+ // Creality DWIN User Data
//
- #if ENABLED(DWIN_CREALITY_LCD_JYERSUI)
+ #if ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ {
+ const char dwin_data[eeprom_data_size] = { 0 };
+ _FIELD_TEST(dwin_data);
+ EEPROM_READ(dwin_data);
+ if (!validating) DWIN_LoadSettings(dwin_data);
+ }
+ #elif ENABLED(DWIN_CREALITY_LCD_JYERSUI)
{
const char dwin_settings[CrealityDWIN.eeprom_data_size] = { 0 };
_FIELD_TEST(dwin_settings);
@@ -2340,6 +2357,7 @@ void MarlinSettings::postprocess() {
else if (working_crc != stored_crc) {
eeprom_error = true;
DEBUG_ERROR_MSG("EEPROM CRC mismatch - (stored) ", stored_crc, " != ", working_crc, " (calculated)!");
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, ui.set_status(GET_TEXT(MSG_ERR_EEPROM_CRC)));
IF_DISABLED(EEPROM_AUTO_INIT, ui.eeprom_alert_crc());
}
else if (!validating) {
@@ -2656,7 +2674,7 @@ void MarlinSettings::reset() {
#endif
TERN_(EXTENSIBLE_UI, ExtUI::onFactoryReset());
-
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_SetDataDefaults());
TERN_(DWIN_CREALITY_LCD_JYERSUI, CrealityDWIN.Reset_Settings());
//
diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp
index 935de772f4..1bb86ed4c7 100644
--- a/Marlin/src/module/temperature.cpp
+++ b/Marlin/src/module/temperature.cpp
@@ -47,6 +47,8 @@
#if ENABLED(DWIN_CREALITY_LCD)
#include "../lcd/e3v2/creality/dwin.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../lcd/e3v2/enhanced/dwin.h"
#endif
#if ENABLED(EXTENSIBLE_UI)
@@ -603,10 +605,12 @@ volatile bool Temperature::raw_temps_ready = false;
TERN_(HAS_AUTO_FAN, next_auto_fan_check_ms = next_temp_ms + 2500UL);
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_STARTED));
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_PidTuning(isbed ? PID_BED_START : PID_EXTR_START));
if (target > GHV(CHAMBER_MAX_TARGET, BED_MAX_TARGET, temp_range[heater_id].maxtemp - (HOTEND_OVERSHOOT))) {
SERIAL_ECHOLNPGM(STR_PID_TEMP_TOO_HIGH);
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_TEMP_TOO_HIGH));
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_PidTuning(PID_TEMP_TOO_HIGH));
return;
}
@@ -627,6 +631,7 @@ volatile bool Temperature::raw_temps_ready = false;
// PID Tuning loop
wait_for_heatup = true; // Can be interrupted with M108
+ TERN_(HAS_STATUS_MESSAGE, ui.status_printf_P(0, PSTR(S_FMT), "Wait for heat up..."));
while (wait_for_heatup) {
const millis_t ms = millis();
@@ -687,6 +692,7 @@ volatile bool Temperature::raw_temps_ready = false;
}
}
SHV((bias + d) >> 1);
+ TERN_(HAS_STATUS_MESSAGE, ui.status_printf_P(0, PSTR(S_FMT " %i/%i"), GET_TEXT(MSG_PID_CYCLE), cycles, ncycles));
cycles++;
minT = target;
}
@@ -699,6 +705,7 @@ volatile bool Temperature::raw_temps_ready = false;
if (current_temp > target + MAX_OVERSHOOT_PID_AUTOTUNE) {
SERIAL_ECHOLNPGM(STR_PID_TEMP_TOO_HIGH);
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_TEMP_TOO_HIGH));
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_PidTuning(PID_TEMP_TOO_HIGH));
break;
}
@@ -734,6 +741,7 @@ volatile bool Temperature::raw_temps_ready = false;
#endif
if ((ms - _MIN(t1, t2)) > (MAX_CYCLE_TIME_PID_AUTOTUNE * 60L * 1000L)) {
TERN_(DWIN_CREALITY_LCD, DWIN_Popup_Temperature(0));
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_PidTuning(PID_TUNING_TIMEOUT));
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_TUNING_TIMEOUT));
SERIAL_ECHOLNPGM(STR_PID_TIMEOUT);
break;
@@ -787,6 +795,7 @@ volatile bool Temperature::raw_temps_ready = false;
TERN_(PRINTER_EVENT_LEDS, printerEventLEDs.onPidTuningDone(color));
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_DONE));
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_PidTuning(PID_DONE));
goto EXIT_M303;
}
@@ -795,7 +804,7 @@ volatile bool Temperature::raw_temps_ready = false;
TERN_(HAL_IDLETASK, HAL_idletask());
// Run UI update
- TERN(DWIN_CREALITY_LCD, DWIN_Update(), ui.update());
+ TERN(HAS_DWIN_E3V2_BASIC, DWIN_Update(), ui.update());
}
wait_for_heatup = false;
@@ -804,6 +813,7 @@ volatile bool Temperature::raw_temps_ready = false;
TERN_(PRINTER_EVENT_LEDS, printerEventLEDs.onPidTuningDone(color));
TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_DONE));
+ TERN_(DWIN_CREALITY_LCD_ENHANCED, DWIN_PidTuning(PID_DONE));
EXIT_M303:
TERN_(NO_FAN_SLOWING_IN_PID_TUNING, adaptive_fan_slowing = true);
@@ -1014,14 +1024,14 @@ void Temperature::_temp_error(const heater_id_t heater_id, PGM_P const serial_ms
}
void Temperature::max_temp_error(const heater_id_t heater_id) {
- #if ENABLED(DWIN_CREALITY_LCD) && (HAS_HOTEND || HAS_HEATED_BED)
+ #if HAS_DWIN_E3V2_BASIC && (HAS_HOTEND || HAS_HEATED_BED)
DWIN_Popup_Temperature(1);
#endif
_temp_error(heater_id, PSTR(STR_T_MAXTEMP), GET_TEXT(MSG_ERR_MAXTEMP));
}
void Temperature::min_temp_error(const heater_id_t heater_id) {
- #if ENABLED(DWIN_CREALITY_LCD) && (HAS_HOTEND || HAS_HEATED_BED)
+ #if HAS_DWIN_E3V2_BASIC && (HAS_HOTEND || HAS_HEATED_BED)
DWIN_Popup_Temperature(0);
#endif
_temp_error(heater_id, PSTR(STR_T_MINTEMP), GET_TEXT(MSG_ERR_MINTEMP));
@@ -1329,7 +1339,7 @@ void Temperature::manage_heater() {
if (watch_hotend[e].check(degHotend(e))) // Increased enough?
start_watching_hotend(e); // If temp reached, turn off elapsed check
else {
- TERN_(DWIN_CREALITY_LCD, DWIN_Popup_Temperature(0));
+ TERN_(HAS_DWIN_E3V2_BASIC, DWIN_Popup_Temperature(0));
_temp_error((heater_id_t)e, str_t_heating_failed, GET_TEXT(MSG_HEATING_FAILED_LCD));
}
}
@@ -1372,7 +1382,7 @@ void Temperature::manage_heater() {
if (watch_bed.check(degBed())) // Increased enough?
start_watching_bed(); // If temp reached, turn off elapsed check
else {
- TERN_(DWIN_CREALITY_LCD, DWIN_Popup_Temperature(0));
+ TERN_(HAS_DWIN_E3V2_BASIC, DWIN_Popup_Temperature(0));
_temp_error(H_BED, str_t_heating_failed, GET_TEXT(MSG_HEATING_FAILED_LCD));
}
}
@@ -2586,7 +2596,7 @@ void Temperature::init() {
state = TRRunaway;
case TRRunaway:
- TERN_(DWIN_CREALITY_LCD, DWIN_Popup_Temperature(0));
+ TERN_(HAS_DWIN_E3V2_BASIC, DWIN_Popup_Temperature(0));
_temp_error(heater_id, str_t_thermal_runaway, GET_TEXT(MSG_THERMAL_RUNAWAY));
}
}
@@ -3600,7 +3610,7 @@ void Temperature::isr() {
#if HAS_MULTI_HOTEND
PSTR("E%c " S_FMT), '1' + e
#else
- PSTR("E " S_FMT)
+ PSTR("E1 " S_FMT)
#endif
, heating ? GET_TEXT(MSG_HEATING) : GET_TEXT(MSG_COOLING)
);
@@ -3720,13 +3730,12 @@ void Temperature::isr() {
if (wait_for_heatup) {
wait_for_heatup = false;
- #if ENABLED(DWIN_CREALITY_LCD)
+ #if HAS_DWIN_E3V2_BASIC
HMI_flag.heat_flag = 0;
duration_t elapsed = print_job_timer.duration(); // print timer
dwin_heat_time = elapsed.value;
- #else
- ui.reset_status();
#endif
+ ui.reset_status();
TERN_(PRINTER_EVENT_LEDS, printerEventLEDs.onHeatingDone());
return true;
}
diff --git a/Marlin/src/pins/stm32f1/pins_BTT_SKR_MINI_E3_common.h b/Marlin/src/pins/stm32f1/pins_BTT_SKR_MINI_E3_common.h
index 9f923b4ea9..48d38e2213 100644
--- a/Marlin/src/pins/stm32f1/pins_BTT_SKR_MINI_E3_common.h
+++ b/Marlin/src/pins/stm32f1/pins_BTT_SKR_MINI_E3_common.h
@@ -148,7 +148,7 @@
* All pins are labeled as printed on DWIN PCB. Connect TX-TX, A-A and so on.
*/
- #error "DWIN_CREALITY_LCD requires a custom cable, see diagram above this line. Comment out this line to continue."
+ #error "Ender-3 V2 display requires a custom cable, see diagram above this line. Comment out this line to continue."
#define BEEPER_PIN EXP1_9
#define BTN_EN1 EXP1_3
diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp
index c0bc81a3ef..832cfa405f 100644
--- a/Marlin/src/sd/cardreader.cpp
+++ b/Marlin/src/sd/cardreader.cpp
@@ -33,6 +33,8 @@
#if ENABLED(DWIN_CREALITY_LCD)
#include "../lcd/e3v2/creality/dwin.h"
+#elif ENABLED(DWIN_CREALITY_LCD_ENHANCED)
+ #include "../lcd/e3v2/enhanced/dwin.h"
#endif
#include "../module/planner.h" // for synchronize
@@ -564,7 +566,7 @@ void CardReader::startOrResumeFilePrinting() {
//
void CardReader::endFilePrintNow(TERN_(SD_RESORT, const bool re_sort/*=false*/)) {
TERN_(ADVANCED_PAUSE_FEATURE, did_pause_print = 0);
- TERN_(DWIN_CREALITY_LCD, HMI_flag.print_finish = flag.sdprinting);
+ TERN_(HAS_DWIN_E3V2_BASIC, HMI_flag.print_finish = flag.sdprinting);
flag.abort_sd_printing = false;
if (isFileOpen()) file.close();
TERN_(SD_RESORT, if (re_sort) presort());
diff --git a/buildroot/tests/STM32F103RET6_creality b/buildroot/tests/STM32F103RET6_creality
index 0ad66bfdb9..277a68411d 100755
--- a/buildroot/tests/STM32F103RET6_creality
+++ b/buildroot/tests/STM32F103RET6_creality
@@ -13,6 +13,11 @@ use_example_configs "Creality/Ender-3 V2/CrealityUI"
opt_enable MARLIN_DEV_MODE BUFFER_MONITORING
exec_test $1 $2 "Ender 3 v2 with CrealityUI" "$3"
+use_example_configs "Creality/Ender-3 V2/CrealityUI"
+opt_disable DWIN_CREALITY_LCD
+opt_enable DWIN_CREALITY_LCD_ENHANCED
+exec_test $1 $2 "Ender 3 v2 with Enhanced UI" "$3"
+
use_example_configs "Creality/Ender-3 V2/CrealityUI"
opt_disable DWIN_CREALITY_LCD
opt_enable DWIN_CREALITY_LCD_JYERSUI
diff --git a/ini/features.ini b/ini/features.ini
index acadd7763a..6ad375e594 100644
--- a/ini/features.ini
+++ b/ini/features.ini
@@ -45,6 +45,7 @@ I2C_EEPROM = src_filter=+
DWIN_CREALITY_LCD = src_filter=+
+DWIN_CREALITY_LCD_ENHANCED = src_filter=+
DWIN_CREALITY_LCD_JYERSUI = src_filter=+
DWIN_MARLINUI_.+ = src_filter=+
HAS_GRAPHICAL_TFT = src_filter=+
diff --git a/platformio.ini b/platformio.ini
index 23ee15d98b..5502069800 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -50,7 +50,7 @@ lib_deps =
default_src_filter = + - - +
- - - - -
- -
- - - -
+ - - - -
-
- - -
- -