From 6cf2cf7bd4bba98dbcb73fca43c14953b977da9a Mon Sep 17 00:00:00 2001 From: Miguel Risco-Castillo Date: Tue, 7 Sep 2021 02:15:24 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Ender-3=20V2=20CrealityUI=20Enhance?= =?UTF-8?q?d=20(#21942,=20#22728,=20#22733)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Scott Lahteine --- Marlin/Configuration.h | 5 + Marlin/src/MarlinCore.cpp | 13 +- Marlin/src/feature/pause.cpp | 13 +- Marlin/src/feature/powerloss.cpp | 2 +- Marlin/src/feature/powerloss.h | 2 +- Marlin/src/feature/runout.cpp | 3 + Marlin/src/gcode/bedlevel/abl/G29.cpp | 17 +- Marlin/src/gcode/bedlevel/mbl/G29.cpp | 3 + Marlin/src/gcode/bedlevel/ubl/M421.cpp | 3 + Marlin/src/gcode/calibrate/G28.cpp | 11 +- Marlin/src/gcode/control/M997.cpp | 6 + Marlin/src/gcode/feature/powerloss/M1000.cpp | 7 +- Marlin/src/gcode/lcd/M0_M1.cpp | 4 + Marlin/src/gcode/lcd/M73.cpp | 28 +- Marlin/src/gcode/sd/M1001.cpp | 3 + Marlin/src/gcode/stats/M75-M78.cpp | 23 +- Marlin/src/gcode/temp/M303.cpp | 3 + Marlin/src/inc/Conditionals_LCD.h | 5 +- Marlin/src/inc/Conditionals_post.h | 4 +- Marlin/src/inc/SanityCheck.h | 18 +- Marlin/src/lcd/e3v2/{creality => }/README.md | 0 Marlin/src/lcd/e3v2/creality/rotary_encoder.h | 12 +- Marlin/src/lcd/e3v2/enhanced/dwin.cpp | 3657 +++++++++++++++++ Marlin/src/lcd/e3v2/enhanced/dwin.h | 265 ++ Marlin/src/lcd/e3v2/enhanced/dwin_lcd.cpp | 570 +++ Marlin/src/lcd/e3v2/enhanced/dwin_lcd.h | 285 ++ Marlin/src/lcd/e3v2/enhanced/dwinui.cpp | 452 ++ Marlin/src/lcd/e3v2/enhanced/dwinui.h | 624 +++ Marlin/src/lcd/e3v2/enhanced/lockscreen.cpp | 69 + Marlin/src/lcd/e3v2/enhanced/lockscreen.h | 35 + .../src/lcd/e3v2/enhanced/rotary_encoder.cpp | 261 ++ Marlin/src/lcd/e3v2/enhanced/rotary_encoder.h | 93 + Marlin/src/lcd/e3v2/jyersui/dwin_lcd.h | 3 - Marlin/src/lcd/language/language_en.h | 17 +- Marlin/src/lcd/marlinui.cpp | 5 +- Marlin/src/lcd/marlinui.h | 21 +- Marlin/src/lcd/tft/ui_common.h | 2 +- Marlin/src/module/probe.cpp | 4 +- Marlin/src/module/settings.cpp | 40 +- Marlin/src/module/temperature.cpp | 29 +- .../stm32f1/pins_BTT_SKR_MINI_E3_common.h | 2 +- Marlin/src/sd/cardreader.cpp | 4 +- buildroot/tests/STM32F103RET6_creality | 5 + ini/features.ini | 1 + platformio.ini | 2 +- 45 files changed, 6545 insertions(+), 86 deletions(-) rename Marlin/src/lcd/e3v2/{creality => }/README.md (100%) create mode 100644 Marlin/src/lcd/e3v2/enhanced/dwin.cpp create mode 100644 Marlin/src/lcd/e3v2/enhanced/dwin.h create mode 100644 Marlin/src/lcd/e3v2/enhanced/dwin_lcd.cpp create mode 100644 Marlin/src/lcd/e3v2/enhanced/dwin_lcd.h create mode 100644 Marlin/src/lcd/e3v2/enhanced/dwinui.cpp create mode 100644 Marlin/src/lcd/e3v2/enhanced/dwinui.h create mode 100644 Marlin/src/lcd/e3v2/enhanced/lockscreen.cpp create mode 100644 Marlin/src/lcd/e3v2/enhanced/lockscreen.h create mode 100644 Marlin/src/lcd/e3v2/enhanced/rotary_encoder.cpp create mode 100644 Marlin/src/lcd/e3v2/enhanced/rotary_encoder.h 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 = + - - + - - - - - - - - - - - + - - - - - - - - - -