My Marlin configs for Fabrikator Mini and CTC i3 Pro B
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ui_480x320.cpp 36KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068
  1. /**
  2. * Marlin 3D Printer Firmware
  3. * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
  4. *
  5. * Based on Sprinter and grbl.
  6. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. *
  21. */
  22. #include "../../inc/MarlinConfigPre.h"
  23. #if HAS_UI_480x320
  24. #include "ui_480x320.h"
  25. #include "../ultralcd.h"
  26. #include "../menu/menu.h"
  27. #include "../../libs/numtostr.h"
  28. #include "../../sd/cardreader.h"
  29. #include "../../module/temperature.h"
  30. #include "../../module/printcounter.h"
  31. #include "../../module/planner.h"
  32. #include "../../module/motion.h"
  33. #if DISABLED(LCD_PROGRESS_BAR) && BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
  34. #include "../../feature/filwidth.h"
  35. #include "../../gcode/parser.h"
  36. #endif
  37. #if ENABLED(AUTO_BED_LEVELING_UBL)
  38. #include "../../feature/bedlevel/bedlevel.h"
  39. #endif
  40. #if !HAS_LCD_MENU
  41. #error "Seriously? High resolution TFT screen without menu?"
  42. #endif
  43. static bool draw_menu_navigation = false;
  44. void MarlinUI::tft_idle() {
  45. #if ENABLED(TOUCH_SCREEN)
  46. if (draw_menu_navigation) {
  47. add_control(104, 286, PAGE_UP, imgPageUp, encoderTopLine > 0);
  48. add_control(344, 286, PAGE_DOWN, imgPageDown, encoderTopLine + LCD_HEIGHT < screen_items);
  49. add_control(224, 286, BACK, imgBack);
  50. draw_menu_navigation = false;
  51. }
  52. #endif
  53. tft.queue.async();
  54. TERN_(TOUCH_SCREEN, touch.idle());
  55. }
  56. void MarlinUI::init_lcd() {
  57. tft.init();
  58. tft.set_font(MENU_FONT_NAME);
  59. #ifdef SYMBOLS_FONT_NAME
  60. tft.add_glyphs(SYMBOLS_FONT_NAME);
  61. #endif
  62. TERN_(TOUCH_SCREEN, touch.init());
  63. clear_lcd();
  64. }
  65. bool MarlinUI::detected() { return true; }
  66. void MarlinUI::clear_lcd() {
  67. #if ENABLED(TOUCH_SCREEN)
  68. touch.reset();
  69. draw_menu_navigation = false;
  70. #endif
  71. tft.queue.reset();
  72. tft.fill(0, 0, TFT_WIDTH, TFT_HEIGHT, COLOR_BACKGROUND);
  73. }
  74. #if ENABLED(SHOW_BOOTSCREEN)
  75. #ifndef BOOTSCREEN_TIMEOUT
  76. #define BOOTSCREEN_TIMEOUT 1500
  77. #endif
  78. #undef BOOTSCREEN_TIMEOUT
  79. #define BOOTSCREEN_TIMEOUT 5000
  80. void MarlinUI::show_bootscreen() {
  81. tft.queue.reset();
  82. tft.canvas(0, 0, TFT_WIDTH, TFT_HEIGHT);
  83. tft.set_background(COLOR_BACKGROUND);
  84. tft.add_image(142, 130, imgBootScreen); // MarlinLogo195x59x16
  85. #ifdef WEBSITE_URL
  86. tft.add_text(8, 250, COLOR_WEBSITE_URL, WEBSITE_URL);
  87. #endif
  88. tft.queue.sync();
  89. safe_delay(BOOTSCREEN_TIMEOUT);
  90. clear_lcd();
  91. }
  92. #endif // SHOW_BOOTSCREEN
  93. void MarlinUI::draw_kill_screen() {
  94. tft.queue.reset();
  95. tft.fill(0, 0, TFT_WIDTH, TFT_HEIGHT, COLOR_KILL_SCREEN_BG);
  96. uint16_t line = 2;
  97. menu_line(line++, COLOR_KILL_SCREEN_BG);
  98. tft_string.set(status_message);
  99. tft_string.trim();
  100. tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_MENU_TEXT, tft_string);
  101. line++;
  102. menu_line(line++, COLOR_KILL_SCREEN_BG);
  103. tft_string.set(GET_TEXT(MSG_HALTED));
  104. tft_string.trim();
  105. tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_MENU_TEXT, tft_string);
  106. menu_line(line++, COLOR_KILL_SCREEN_BG);
  107. tft_string.set(GET_TEXT(MSG_PLEASE_RESET));
  108. tft_string.trim();
  109. tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_MENU_TEXT, tft_string);
  110. tft.queue.sync();
  111. }
  112. void draw_heater_status(uint16_t x, uint16_t y, const int8_t Heater) {
  113. MarlinImage image = imgHotEnd;
  114. uint16_t Color;
  115. float currentTemperature, targetTemperature;
  116. if (Heater >= 0) { // HotEnd
  117. currentTemperature = thermalManager.degHotend(Heater);
  118. targetTemperature = thermalManager.degTargetHotend(Heater);
  119. }
  120. #if HAS_HEATED_BED
  121. else if (Heater == H_BED) {
  122. currentTemperature = thermalManager.degBed();
  123. targetTemperature = thermalManager.degTargetBed();
  124. }
  125. #endif // HAS_HEATED_BED
  126. #if HAS_TEMP_CHAMBER
  127. else if (Heater == H_CHAMBER) {
  128. currentTemperature = thermalManager.degChamber();
  129. #if HAS_HEATED_CHAMBER
  130. targetTemperature = thermalManager.degTargetChamber();
  131. #else
  132. targetTemperature = ABSOLUTE_ZERO;
  133. #endif
  134. }
  135. #endif // HAS_TEMP_CHAMBER
  136. else return;
  137. TERN_(TOUCH_SCREEN, if (targetTemperature >= 0) touch.add_control(HEATER, x, y, 80, 120, Heater));
  138. tft.canvas(x, y, 80, 120);
  139. tft.set_background(COLOR_BACKGROUND);
  140. Color = currentTemperature < 0 ? COLOR_INACTIVE : COLOR_COLD;
  141. if (Heater >= 0) { // HotEnd
  142. if (currentTemperature >= 50) Color = COLOR_HOTEND;
  143. }
  144. #if HAS_HEATED_BED
  145. else if (Heater == H_BED) {
  146. if (currentTemperature >= 50) Color = COLOR_HEATED_BED;
  147. image = targetTemperature > 0 ? imgBedHeated : imgBed;
  148. }
  149. #endif // HAS_HEATED_BED
  150. #if HAS_TEMP_CHAMBER
  151. else if (Heater == H_CHAMBER) {
  152. if (currentTemperature >= 50) Color = COLOR_CHAMBER;
  153. image = targetTemperature > 0 ? imgChamberHeated : imgChamber;
  154. }
  155. #endif // HAS_TEMP_CHAMBER
  156. tft.add_image(8, 28, image, Color);
  157. tft_string.set((uint8_t *)i16tostr3rj(currentTemperature + 0.5));
  158. tft_string.add(LCD_STR_DEGREE);
  159. tft_string.trim();
  160. tft.add_text(tft_string.center(80) + 2, 82, Color, tft_string);
  161. if (targetTemperature >= 0) {
  162. tft_string.set((uint8_t *)i16tostr3rj(targetTemperature + 0.5));
  163. tft_string.add(LCD_STR_DEGREE);
  164. tft_string.trim();
  165. tft.add_text(tft_string.center(80) + 2, 8, Color, tft_string);
  166. }
  167. }
  168. void draw_fan_status(uint16_t x, uint16_t y, const bool blink) {
  169. TERN_(TOUCH_SCREEN, touch.add_control(FAN, x, y, 80, 120));
  170. tft.canvas(x, y, 80, 120);
  171. tft.set_background(COLOR_BACKGROUND);
  172. uint8_t fanSpeed = thermalManager.fan_speed[0];
  173. MarlinImage image;
  174. if (fanSpeed >= 127)
  175. image = blink ? imgFanFast1 : imgFanFast0;
  176. else if (fanSpeed > 0)
  177. image = blink ? imgFanSlow1 : imgFanSlow0;
  178. else
  179. image = imgFanIdle;
  180. tft.add_image(8, 20, image, COLOR_FAN);
  181. tft_string.set((uint8_t *)ui8tostr4pctrj(thermalManager.fan_speed[0]));
  182. tft_string.trim();
  183. tft.add_text(tft_string.center(80) + 6, 82, COLOR_FAN, tft_string);
  184. }
  185. void MarlinUI::draw_status_screen() {
  186. const bool blink = get_blink();
  187. TERN_(TOUCH_SCREEN, touch.clear());
  188. // heaters and fan
  189. uint16_t i, x, y = POS_Y;
  190. for (i = 0 ; i < ITEMS_COUNT; i++) {
  191. x = (TFT_WIDTH / ITEMS_COUNT - 80) / 2 + (TFT_WIDTH * i / ITEMS_COUNT);
  192. switch (i) {
  193. #ifdef ITEM_E0
  194. case ITEM_E0: draw_heater_status(x, y, H_E0); break;
  195. #endif
  196. #ifdef ITEM_E1
  197. case ITEM_E1: draw_heater_status(x, y, H_E1); break;
  198. #endif
  199. #ifdef ITEM_E2
  200. case ITEM_E2: draw_heater_status(x, y, H_E2); break;
  201. #endif
  202. #ifdef ITEM_BED
  203. case ITEM_BED: draw_heater_status(x, y, H_BED); break;
  204. #endif
  205. #ifdef ITEM_CHAMBER
  206. case ITEM_CHAMBER: draw_heater_status(x, y, H_CHAMBER); break;
  207. #endif
  208. #ifdef ITEM_FAN
  209. case ITEM_FAN: draw_fan_status(x, y, blink); break;
  210. #endif
  211. }
  212. }
  213. // coordinates
  214. tft.canvas(4, 132, TFT_WIDTH - 8, 34);
  215. tft.set_background(COLOR_BACKGROUND);
  216. tft.add_rectangle(0, 0, TFT_WIDTH - 8, 34, COLOR_AXIS_HOMED);
  217. uint16_t color;
  218. uint16_t offset;
  219. bool is_homed;
  220. tft.add_text( 16, 3, COLOR_AXIS_HOMED , "X");
  221. tft.add_text(192, 3, COLOR_AXIS_HOMED , "Y");
  222. tft.add_text(330, 3, COLOR_AXIS_HOMED , "Z");
  223. is_homed = TEST(axis_homed, X_AXIS);
  224. tft_string.set(blink & !is_homed ? "?" : ftostr4sign(LOGICAL_X_POSITION(current_position.x)));
  225. tft.add_text(102 - tft_string.width(), 3, is_homed ? COLOR_AXIS_HOMED : COLOR_AXIS_NOT_HOMED, tft_string);
  226. is_homed = TEST(axis_homed, Y_AXIS);
  227. tft_string.set(blink & !is_homed ? "?" : ftostr4sign(LOGICAL_Y_POSITION(current_position.y)));
  228. tft.add_text(280 - tft_string.width(), 3, is_homed ? COLOR_AXIS_HOMED : COLOR_AXIS_NOT_HOMED, tft_string);
  229. is_homed = TEST(axis_homed, Z_AXIS);
  230. if (blink & !is_homed) {
  231. tft_string.set("?");
  232. offset = 32; // ".00"
  233. }
  234. else {
  235. const float z = LOGICAL_Z_POSITION(current_position.z);
  236. tft_string.set(ftostr52sp((int16_t)z));
  237. tft_string.rtrim();
  238. offset = tft_string.width();
  239. tft_string.set(ftostr52sp(z));
  240. offset += 32 - tft_string.width();
  241. }
  242. tft.add_text(455 - tft_string.width() - offset, 3, is_homed ? COLOR_AXIS_HOMED : COLOR_AXIS_NOT_HOMED, tft_string);
  243. TERN_(TOUCH_SCREEN, touch.add_control(MOVE_AXIS, 4, 132, TFT_WIDTH - 8, 34));
  244. // feed rate
  245. tft.canvas(96, 180, 100, 32);
  246. tft.set_background(COLOR_BACKGROUND);
  247. color = feedrate_percentage == 100 ? COLOR_RATE_100 : COLOR_RATE_ALTERED;
  248. tft.add_image(0, 0, imgFeedRate, color);
  249. tft_string.set(i16tostr3rj(feedrate_percentage));
  250. tft_string.add('%');
  251. tft.add_text(36, 1, color , tft_string);
  252. TERN_(TOUCH_SCREEN, touch.add_control(FEEDRATE, 96, 176, 100, 32));
  253. // flow rate
  254. tft.canvas(284, 180, 100, 32);
  255. tft.set_background(COLOR_BACKGROUND);
  256. color = planner.flow_percentage[0] == 100 ? COLOR_RATE_100 : COLOR_RATE_ALTERED;
  257. tft.add_image(0, 0, imgFlowRate, color);
  258. tft_string.set(i16tostr3rj(planner.flow_percentage[active_extruder]));
  259. tft_string.add('%');
  260. tft.add_text(36, 1, color , tft_string);
  261. TERN_(TOUCH_SCREEN, touch.add_control(FLOWRATE, 284, 176, 100, 32, active_extruder));
  262. // print duration
  263. char buffer[14];
  264. duration_t elapsed = print_job_timer.duration();
  265. elapsed.toDigital(buffer);
  266. tft.canvas((TFT_WIDTH - 128) / 2, 224, 128, 29);
  267. tft.set_background(COLOR_BACKGROUND);
  268. tft_string.set(buffer);
  269. tft.add_text(tft_string.center(128), 0, COLOR_PRINT_TIME, tft_string);
  270. // progress bar
  271. const uint8_t progress = ui.get_progress_percent();
  272. tft.canvas(4, 260, TFT_WIDTH - 8, 9);
  273. tft.set_background(COLOR_PROGRESS_BG);
  274. tft.add_rectangle(0, 0, TFT_WIDTH - 8, 9, COLOR_PROGRESS_FRAME);
  275. if (progress)
  276. tft.add_bar(1, 1, ((TFT_WIDTH - 10) * progress) / 100, 7, COLOR_PROGRESS_BAR);
  277. // status message
  278. tft.canvas(0, 280, TFT_WIDTH, 29);
  279. tft.set_background(COLOR_BACKGROUND);
  280. tft_string.set(status_message);
  281. tft_string.trim();
  282. tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_STATUS_MESSAGE, tft_string);
  283. #if ENABLED(TOUCH_SCREEN)
  284. add_control(404, 180, menu_main, imgSettings);
  285. TERN_(SDSUPPORT, add_control(12, 180, menu_media, imgSD, card.isMounted() && !printingIsActive(), COLOR_CONTROL_ENABLED, card.isMounted() && printingIsActive() ? COLOR_BUSY : COLOR_CONTROL_DISABLED));
  286. #endif
  287. }
  288. // Draw a static item with no left-right margin required. Centered by default.
  289. void MenuItem_static::draw(const uint8_t row, PGM_P const pstr, const uint8_t style/*=SS_DEFAULT*/, const char * const vstr/*=nullptr*/) {
  290. menu_item(row);
  291. tft_string.set(pstr, itemIndex, itemString);
  292. if (vstr)
  293. tft_string.add(vstr);
  294. tft.add_text(tft_string.center(TFT_WIDTH), MENU_TEXT_Y_OFFSET, COLOR_YELLOW, tft_string);
  295. }
  296. // Draw a generic menu item with pre_char (if selected) and post_char
  297. void MenuItemBase::_draw(const bool sel, const uint8_t row, PGM_P const pstr, const char pre_char, const char post_char) {
  298. menu_item(row, sel);
  299. uint8_t *string = (uint8_t *)pstr;
  300. MarlinImage image = noImage;
  301. switch (*string) {
  302. case 0x01: image = imgRefresh; break; // LCD_STR_REFRESH
  303. case 0x02: image = imgDirectory; break; // LCD_STR_FOLDER
  304. }
  305. uint8_t offset = MENU_TEXT_X_OFFSET;
  306. if (image != noImage) {
  307. string++;
  308. offset = 42;
  309. tft.add_image(5, 5, image, COLOR_MENU_TEXT, sel ? COLOR_SELECTION_BG : COLOR_BACKGROUND);
  310. }
  311. tft_string.set(string, itemIndex, itemString);
  312. tft.add_text(offset, MENU_TEXT_Y_OFFSET, COLOR_MENU_TEXT, tft_string);
  313. }
  314. // Draw a menu item with a (potentially) editable value
  315. void MenuEditItemBase::draw(const bool sel, const uint8_t row, PGM_P const pstr, const char* const data, const bool pgm) {
  316. menu_item(row, sel);
  317. tft_string.set(pstr, itemIndex, itemString);
  318. tft.add_text(MENU_TEXT_X_OFFSET, MENU_TEXT_Y_OFFSET, COLOR_MENU_TEXT, tft_string);
  319. if (data) {
  320. tft_string.set(data);
  321. tft.add_text(TFT_WIDTH - MENU_TEXT_X_OFFSET - tft_string.width(), MENU_TEXT_Y_OFFSET, COLOR_MENU_VALUE, tft_string);
  322. }
  323. }
  324. // Low-level draw_edit_screen can be used to draw an edit screen from anyplace
  325. void MenuEditItemBase::draw_edit_screen(PGM_P const pstr, const char* const value/*=nullptr*/) {
  326. ui.encoder_direction_normal();
  327. TERN_(TOUCH_SCREEN, touch.clear());
  328. uint16_t line = 1;
  329. menu_line(line++);
  330. tft_string.set(pstr, itemIndex, itemString);
  331. tft_string.trim();
  332. tft.add_text(tft_string.center(TFT_WIDTH), MENU_TEXT_Y_OFFSET, COLOR_MENU_TEXT, tft_string);
  333. TERN_(AUTO_BED_LEVELING_UBL, if (ui.external_control) line++); // ftostr52() will overwrite *value so *value has to be displayed first
  334. menu_line(line);
  335. tft_string.set(value);
  336. tft_string.trim();
  337. tft.add_text(tft_string.center(TFT_WIDTH), MENU_TEXT_Y_OFFSET, COLOR_MENU_VALUE, tft_string);
  338. #if ENABLED(AUTO_BED_LEVELING_UBL)
  339. if (ui.external_control) {
  340. menu_line(line - 1);
  341. tft_string.set(X_LBL);
  342. tft.add_text((TFT_WIDTH / 2 - 120), MENU_TEXT_Y_OFFSET, COLOR_MENU_TEXT, tft_string);
  343. tft_string.set(ftostr52(LOGICAL_X_POSITION(current_position.x)));
  344. tft_string.trim();
  345. tft.add_text((TFT_WIDTH / 2 - 16) - tft_string.width(), MENU_TEXT_Y_OFFSET, COLOR_MENU_VALUE, tft_string);
  346. tft_string.set(Y_LBL);
  347. tft.add_text((TFT_WIDTH / 2 + 16), MENU_TEXT_Y_OFFSET, COLOR_MENU_TEXT, tft_string);
  348. tft_string.set(ftostr52(LOGICAL_X_POSITION(current_position.y)));
  349. tft_string.trim();
  350. tft.add_text((TFT_WIDTH / 2 + 120) - tft_string.width(), MENU_TEXT_Y_OFFSET, COLOR_MENU_VALUE, tft_string);
  351. }
  352. #endif
  353. extern screenFunc_t _manual_move_func_ptr;
  354. if (ui.currentScreen != _manual_move_func_ptr && !ui.external_control) {
  355. #define SLIDER_LENGHT 336
  356. #define SLIDER_Y_POSITION 186
  357. tft.canvas((TFT_WIDTH - SLIDER_LENGHT) / 2, SLIDER_Y_POSITION, SLIDER_LENGHT, 16);
  358. tft.set_background(COLOR_BACKGROUND);
  359. int16_t position = (SLIDER_LENGHT - 2) * ui.encoderPosition / maxEditValue;
  360. tft.add_bar(0, 7, 1, 2, ui.encoderPosition == 0 ? COLOR_SLIDER_INACTIVE : COLOR_SLIDER);
  361. tft.add_bar(1, 6, position, 4, COLOR_SLIDER);
  362. tft.add_bar(position + 1, 6, SLIDER_LENGHT - 2 - position, 4, COLOR_SLIDER_INACTIVE);
  363. tft.add_bar(SLIDER_LENGHT - 1, 7, 1, 2, int32_t(ui.encoderPosition) == maxEditValue ? COLOR_SLIDER : COLOR_SLIDER_INACTIVE);
  364. #if ENABLED(TOUCH_SCREEN)
  365. tft.add_image((SLIDER_LENGHT - 8) * ui.encoderPosition / maxEditValue, 0, imgSlider, COLOR_SLIDER);
  366. touch.add_control(SLIDER, (TFT_WIDTH - SLIDER_LENGHT) / 2, SLIDER_Y_POSITION - 8, SLIDER_LENGHT, 32, maxEditValue);
  367. #endif
  368. }
  369. #if ENABLED(TOUCH_SCREEN)
  370. add_control(64, 256, DECREASE, imgDecrease);
  371. add_control(352, 256, INCREASE, imgIncrease);
  372. add_control(208, 256, CLICK, imgConfirm);
  373. #endif
  374. }
  375. // The Select Screen presents a prompt and two "buttons"
  376. void MenuItem_confirm::draw_select_screen(PGM_P const yes, PGM_P const no, const bool yesno, PGM_P const pref, const char * const string/*=nullptr*/, PGM_P const suff/*=nullptr*/) {
  377. uint16_t line = 1;
  378. if (string == NULL) line++;
  379. menu_line(line++);
  380. tft_string.set(pref);
  381. tft_string.trim();
  382. tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_MENU_TEXT, tft_string);
  383. if (string) {
  384. menu_line(line++);
  385. tft_string.set(string);
  386. tft_string.trim();
  387. tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_MENU_TEXT, tft_string);
  388. }
  389. if (suff) {
  390. menu_line(line);
  391. tft_string.set(suff);
  392. tft_string.trim();
  393. tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_MENU_TEXT, tft_string);
  394. }
  395. #if ENABLED(TOUCH_SCREEN)
  396. add_control(88, 256, CANCEL, imgCancel, true, yesno ? HALF(COLOR_CONTROL_CANCEL) : COLOR_CONTROL_CANCEL);
  397. add_control(328, 256, CONFIRM, imgConfirm, true, yesno ? COLOR_CONTROL_CONFIRM : HALF(COLOR_CONTROL_CONFIRM));
  398. #endif
  399. }
  400. #if ENABLED(SDSUPPORT)
  401. void MenuItem_sdbase::draw(const bool sel, const uint8_t row, PGM_P const, CardReader &theCard, const bool isDir) {
  402. menu_item(row, sel);
  403. if (isDir)
  404. tft.add_image(5, 5, imgDirectory, COLOR_MENU_TEXT, sel ? COLOR_SELECTION_BG : COLOR_BACKGROUND);
  405. tft.add_text(42, MENU_TEXT_Y_OFFSET, COLOR_MENU_TEXT, theCard.longest_filename());
  406. }
  407. #endif
  408. #if ENABLED(ADVANCED_PAUSE_FEATURE)
  409. void MarlinUI::draw_hotend_status(const uint8_t row, const uint8_t extruder) {
  410. #if ENABLED(TOUCH_SCREEN)
  411. touch.clear();
  412. draw_menu_navigation = false;
  413. touch.add_control(RESUME_CONTINUE , 0, 0, TFT_WIDTH, TFT_HEIGHT);
  414. #endif
  415. menu_line(row);
  416. tft_string.set(GET_TEXT(MSG_FILAMENT_CHANGE_NOZZLE));
  417. tft_string.add('E');
  418. tft_string.add((char)('1' + extruder));
  419. tft_string.add(' ');
  420. tft_string.add(i16tostr3rj(thermalManager.degHotend(extruder)));
  421. tft_string.add(LCD_STR_DEGREE);
  422. tft_string.add(" / ");
  423. tft_string.add(i16tostr3rj(thermalManager.degTargetHotend(extruder)));
  424. tft_string.add(LCD_STR_DEGREE);
  425. tft_string.trim();
  426. tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_MENU_TEXT, tft_string);
  427. }
  428. #endif // ADVANCED_PAUSE_FEATURE
  429. #if ENABLED(AUTO_BED_LEVELING_UBL)
  430. #define GRID_OFFSET_X 8
  431. #define GRID_OFFSET_Y 8
  432. #define GRID_WIDTH 192
  433. #define GRID_HEIGHT 192
  434. #define CONTROL_OFFSET 16
  435. void MarlinUI::ubl_plot(const uint8_t x_plot, const uint8_t y_plot) {
  436. tft.canvas(GRID_OFFSET_X, GRID_OFFSET_Y, GRID_WIDTH, GRID_HEIGHT);
  437. tft.set_background(COLOR_BACKGROUND);
  438. tft.add_rectangle(0, 0, GRID_WIDTH, GRID_HEIGHT, COLOR_WHITE);
  439. for (uint16_t x = 0; x < GRID_MAX_POINTS_X ; x++)
  440. for (uint16_t y = 0; y < GRID_MAX_POINTS_Y ; y++)
  441. if (position_is_reachable({ ubl.mesh_index_to_xpos(x), ubl.mesh_index_to_ypos(y) }))
  442. tft.add_bar(1 + (x * 2 + 1) * (GRID_WIDTH - 4) / GRID_MAX_POINTS_X / 2, GRID_HEIGHT - 3 - ((y * 2 + 1) * (GRID_HEIGHT - 4) / GRID_MAX_POINTS_Y / 2), 2, 2, COLOR_UBL);
  443. tft.add_rectangle((x_plot * 2 + 1) * (GRID_WIDTH - 4) / GRID_MAX_POINTS_X / 2 - 1, GRID_HEIGHT - 5 - ((y_plot * 2 + 1) * (GRID_HEIGHT - 4) / GRID_MAX_POINTS_Y / 2), 6, 6, COLOR_UBL);
  444. const xy_pos_t pos = { ubl.mesh_index_to_xpos(x_plot), ubl.mesh_index_to_ypos(y_plot) },
  445. lpos = pos.asLogical();
  446. tft.canvas(320, GRID_OFFSET_Y + (GRID_HEIGHT - 43) / 2 - 43, 120, 43);
  447. tft.set_background(COLOR_BACKGROUND);
  448. tft_string.set(X_LBL);
  449. tft.add_text(0, MENU_TEXT_Y_OFFSET, COLOR_MENU_TEXT, tft_string);
  450. tft_string.set(ftostr52(lpos.x));
  451. tft_string.trim();
  452. tft.add_text(120 - tft_string.width(), MENU_TEXT_Y_OFFSET, COLOR_MENU_VALUE, tft_string);
  453. tft.canvas(320, GRID_OFFSET_Y + (GRID_HEIGHT - 43) / 2, 120, 43);
  454. tft.set_background(COLOR_BACKGROUND);
  455. tft_string.set(Y_LBL);
  456. tft.add_text(0, MENU_TEXT_Y_OFFSET, COLOR_MENU_TEXT, tft_string);
  457. tft_string.set(ftostr52(lpos.y));
  458. tft_string.trim();
  459. tft.add_text(120 - tft_string.width(), MENU_TEXT_Y_OFFSET, COLOR_MENU_VALUE, tft_string);
  460. tft.canvas(320, GRID_OFFSET_Y + (GRID_HEIGHT - 43) / 2 + 43, 120, 43);
  461. tft.set_background(COLOR_BACKGROUND);
  462. tft_string.set(Z_LBL);
  463. tft.add_text(0, MENU_TEXT_Y_OFFSET, COLOR_MENU_TEXT, tft_string);
  464. tft_string.set(isnan(ubl.z_values[x_plot][y_plot]) ? "-----" : ftostr43sign(ubl.z_values[x_plot][y_plot]));
  465. tft_string.trim();
  466. tft.add_text(120 - tft_string.width(), MENU_TEXT_Y_OFFSET, COLOR_MENU_VALUE, tft_string);
  467. tft.canvas(GRID_OFFSET_X + (GRID_WIDTH - 48) / 2, GRID_OFFSET_Y + GRID_HEIGHT + CONTROL_OFFSET - 5, 48, 43);
  468. tft.set_background(COLOR_BACKGROUND);
  469. tft_string.set(ui8tostr3rj(x_plot));
  470. tft_string.trim();
  471. tft.add_text(tft_string.center(48), MENU_TEXT_Y_OFFSET, COLOR_MENU_VALUE, tft_string);
  472. tft.canvas(GRID_OFFSET_X + GRID_WIDTH + CONTROL_OFFSET + 16 - 24, GRID_OFFSET_Y + (GRID_HEIGHT - 43) / 2, 48, 43);
  473. tft.set_background(COLOR_BACKGROUND);
  474. tft_string.set(ui8tostr3rj(y_plot));
  475. tft_string.trim();
  476. tft.add_text(tft_string.center(48), MENU_TEXT_Y_OFFSET, COLOR_MENU_VALUE, tft_string);
  477. #if ENABLED(TOUCH_SCREEN)
  478. touch.clear();
  479. draw_menu_navigation = false;
  480. add_control(GRID_OFFSET_X + GRID_WIDTH + CONTROL_OFFSET, GRID_OFFSET_Y + CONTROL_OFFSET, UBL, ENCODER_STEPS_PER_MENU_ITEM * GRID_MAX_POINTS_X, imgUp);
  481. add_control(GRID_OFFSET_X + GRID_WIDTH + CONTROL_OFFSET, GRID_OFFSET_Y + GRID_HEIGHT - CONTROL_OFFSET - 32, UBL, - ENCODER_STEPS_PER_MENU_ITEM * GRID_MAX_POINTS_X, imgDown);
  482. add_control(GRID_OFFSET_X + CONTROL_OFFSET, GRID_OFFSET_Y + GRID_HEIGHT + CONTROL_OFFSET, UBL, - ENCODER_STEPS_PER_MENU_ITEM, imgLeft);
  483. add_control(GRID_OFFSET_X + GRID_WIDTH - CONTROL_OFFSET - 32, GRID_OFFSET_Y + GRID_HEIGHT + CONTROL_OFFSET, UBL, ENCODER_STEPS_PER_MENU_ITEM, imgRight);
  484. add_control(320, GRID_OFFSET_Y + GRID_HEIGHT + CONTROL_OFFSET, CLICK, imgLeveling);
  485. add_control(224, 286, BACK, imgBack);
  486. #endif
  487. }
  488. #endif // AUTO_BED_LEVELING_UBL
  489. #if ENABLED(TOUCH_SCREEN_CALIBRATION)
  490. void MarlinUI::touch_calibration() {
  491. static uint16_t x, y;
  492. calibrationState calibration_stage = touch.get_calibration_state();
  493. if (calibration_stage == CALIBRATION_NONE) {
  494. defer_status_screen(true);
  495. clear_lcd();
  496. calibration_stage = touch.calibration_start();
  497. }
  498. else {
  499. tft.canvas(x - 15, y - 15, 31, 31);
  500. tft.set_background(COLOR_BACKGROUND);
  501. }
  502. x = 20; y = 20;
  503. touch.clear();
  504. if (calibration_stage < CALIBRATION_SUCCESS) {
  505. switch (calibration_stage) {
  506. case CALIBRATION_POINT_1: tft_string.set("Top Left"); break;
  507. case CALIBRATION_POINT_2: y = TFT_HEIGHT - 21; tft_string.set("Bottom Left"); break;
  508. case CALIBRATION_POINT_3: x = TFT_WIDTH - 21; tft_string.set("Top Right"); break;
  509. case CALIBRATION_POINT_4: x = TFT_WIDTH - 21; y = TFT_HEIGHT - 21; tft_string.set("Bottom Right"); break;
  510. default: break;
  511. }
  512. tft.canvas(x - 15, y - 15, 31, 31);
  513. tft.set_background(COLOR_BACKGROUND);
  514. tft.add_bar(0, 15, 31, 1, COLOR_TOUCH_CALIBRATION);
  515. tft.add_bar(15, 0, 1, 31, COLOR_TOUCH_CALIBRATION);
  516. touch.add_control(CALIBRATE, 0, 0, TFT_WIDTH, TFT_HEIGHT, uint32_t(x) << 16 | uint32_t(y));
  517. }
  518. else {
  519. tft_string.set(calibration_stage == CALIBRATION_SUCCESS ? "Calibration Completed" : "Calibration Failed");
  520. defer_status_screen(false);
  521. touch.calibration_end();
  522. touch.add_control(BACK, 0, 0, TFT_WIDTH, TFT_HEIGHT);
  523. }
  524. tft.canvas(0, (TFT_HEIGHT - tft_string.font_height()) >> 1, TFT_WIDTH, tft_string.font_height());
  525. tft.set_background(COLOR_BACKGROUND);
  526. tft.add_text(tft_string.center(TFT_WIDTH), 0, COLOR_MENU_TEXT, tft_string);
  527. }
  528. #endif // TOUCH_SCREEN_CALIBRATION
  529. void menu_line(const uint8_t row, uint16_t color) {
  530. tft.canvas(0, 4 + 45 * row, TFT_WIDTH, 43);
  531. tft.set_background(color);
  532. }
  533. void menu_pause_option();
  534. void menu_item(const uint8_t row, bool sel ) {
  535. #if ENABLED(TOUCH_SCREEN)
  536. if (row == 0) {
  537. touch.clear();
  538. draw_menu_navigation = TERN(ADVANCED_PAUSE_FEATURE, ui.currentScreen != menu_pause_option, true);
  539. }
  540. #endif
  541. menu_line(row, sel ? COLOR_SELECTION_BG : COLOR_BACKGROUND);
  542. TERN_(TOUCH_SCREEN, touch.add_control(sel ? CLICK : MENU_ITEM, 0, 4 + 45 * row, TFT_WIDTH, 43, encoderTopLine + row));
  543. }
  544. #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
  545. #include "../../feature/babystep.h"
  546. #endif
  547. #if HAS_BED_PROBE
  548. #include "../../module/probe.h"
  549. #endif
  550. #define Z_SELECTION_Z 1
  551. #define Z_SELECTION_Z_PROBE -1
  552. struct MotionAxisState {
  553. xy_int_t xValuePos, yValuePos, zValuePos, eValuePos, stepValuePos, zTypePos, eNamePos;
  554. float currentStepSize = 10.0;
  555. int z_selection = Z_SELECTION_Z;
  556. uint8_t e_selection = 0;
  557. bool homming = false;
  558. bool blocked = false;
  559. char message[32];
  560. };
  561. MotionAxisState motionAxisState;
  562. #define E_BTN_COLOR COLOR_YELLOW
  563. #define X_BTN_COLOR COLOR_CORAL_RED
  564. #define Y_BTN_COLOR COLOR_VIVID_GREEN
  565. #define Z_BTN_COLOR COLOR_LIGHT_BLUE
  566. #define BTN_WIDTH 64
  567. #define BTN_HEIGHT 52
  568. #define X_MARGIN 20
  569. #define Y_MARGIN 15
  570. static void quick_feedback() {
  571. #if HAS_CHIRP
  572. ui.chirp(); // Buzz and wait. Is the delay needed for buttons to settle?
  573. #if BOTH(HAS_LCD_MENU, USE_BEEPER)
  574. for (int8_t i = 5; i--;) { buzzer.tick(); delay(2); }
  575. #elif HAS_LCD_MENU
  576. delay(10);
  577. #endif
  578. #endif
  579. }
  580. #define CUR_STEP_VALUE_WIDTH 104
  581. static void drawCurStepValue() {
  582. tft_string.set((uint8_t *)ftostr52sp(motionAxisState.currentStepSize));
  583. tft_string.add("mm");
  584. tft.canvas(motionAxisState.stepValuePos.x, motionAxisState.stepValuePos.y, CUR_STEP_VALUE_WIDTH, BTN_HEIGHT);
  585. tft.set_background(COLOR_BACKGROUND);
  586. tft.add_text(tft_string.center(CUR_STEP_VALUE_WIDTH), 0, COLOR_AXIS_HOMED, tft_string);
  587. }
  588. static void drawCurZSelection() {
  589. tft_string.set("Z");
  590. tft.canvas(motionAxisState.zTypePos.x, motionAxisState.zTypePos.y, tft_string.width(), 34);
  591. tft.set_background(COLOR_BACKGROUND);
  592. tft.add_text(0, 0, Z_BTN_COLOR, tft_string);
  593. tft.queue.sync();
  594. tft_string.set("Offset");
  595. tft.canvas(motionAxisState.zTypePos.x, motionAxisState.zTypePos.y + 34, tft_string.width(), 34);
  596. tft.set_background(COLOR_BACKGROUND);
  597. if (motionAxisState.z_selection == Z_SELECTION_Z_PROBE) {
  598. tft.add_text(0, 0, Z_BTN_COLOR, tft_string);
  599. }
  600. }
  601. static void drawCurESelection() {
  602. tft.canvas(motionAxisState.eNamePos.x, motionAxisState.eNamePos.y, BTN_WIDTH, BTN_HEIGHT);
  603. tft.set_background(COLOR_BACKGROUND);
  604. tft_string.set("E");
  605. tft.add_text(0, 0, E_BTN_COLOR , tft_string);
  606. tft.add_text(tft_string.width(), 0, E_BTN_COLOR, ui8tostr3rj(motionAxisState.e_selection));
  607. }
  608. static void drawMessage(const char *msg) {
  609. tft.canvas(X_MARGIN, TFT_HEIGHT - Y_MARGIN - 34, TFT_HEIGHT / 2, 34);
  610. tft.set_background(COLOR_BACKGROUND);
  611. tft.add_text(0, 0, COLOR_YELLOW, msg);
  612. }
  613. static void drawAxisValue(AxisEnum axis) {
  614. const float value =
  615. #if HAS_BED_PROBE
  616. axis == Z_AXIS && motionAxisState.z_selection == Z_SELECTION_Z_PROBE ?
  617. probe.offset.z :
  618. #endif
  619. NATIVE_TO_LOGICAL(
  620. ui.manual_move.processing ? destination[axis] : current_position[axis] + TERN0(IS_KINEMATIC, ui.manual_move.offset),
  621. axis
  622. );
  623. xy_int_t pos;
  624. uint16_t color;
  625. switch (axis) {
  626. case X_AXIS: pos = motionAxisState.xValuePos; color = X_BTN_COLOR; break;
  627. case Y_AXIS: pos = motionAxisState.yValuePos; color = Y_BTN_COLOR; break;
  628. case Z_AXIS: pos = motionAxisState.zValuePos; color = Z_BTN_COLOR; break;
  629. case E_AXIS: pos = motionAxisState.eValuePos; color = E_BTN_COLOR; break;
  630. default: return;
  631. }
  632. tft.canvas(pos.x, pos.y, BTN_WIDTH + X_MARGIN, BTN_HEIGHT);
  633. tft.set_background(COLOR_BACKGROUND);
  634. tft_string.set(ftostr52sp(value));
  635. tft.add_text(0, 0, color, tft_string);
  636. }
  637. static void moveAxis(AxisEnum axis, const int8_t direction) {
  638. quick_feedback();
  639. if (axis == E_AXIS && thermalManager.temp_hotend[motionAxisState.e_selection].celsius < EXTRUDE_MINTEMP) {
  640. drawMessage("Too cold");
  641. return;
  642. }
  643. const float diff = motionAxisState.currentStepSize * direction;
  644. if (axis == Z_AXIS && motionAxisState.z_selection == Z_SELECTION_Z_PROBE) {
  645. #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
  646. const int16_t babystep_increment = direction * BABYSTEP_SIZE_Z;
  647. const bool do_probe = DISABLED(BABYSTEP_HOTEND_Z_OFFSET) || active_extruder == 0;
  648. const float bsDiff = planner.steps_to_mm[Z_AXIS] * babystep_increment,
  649. new_probe_offset = probe.offset.z + bsDiff,
  650. new_offs = TERN(BABYSTEP_HOTEND_Z_OFFSET
  651. , do_probe ? new_probe_offset : hotend_offset[active_extruder].z - bsDiff
  652. , new_probe_offset
  653. );
  654. if (WITHIN(new_offs, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX)) {
  655. babystep.add_steps(Z_AXIS, babystep_increment);
  656. if (do_probe)
  657. probe.offset.z = new_offs;
  658. else
  659. TERN(BABYSTEP_HOTEND_Z_OFFSET, hotend_offset[active_extruder].z = new_offs, NOOP);
  660. drawMessage(""); // clear the error
  661. drawAxisValue(axis);
  662. }
  663. else {
  664. drawMessage(GET_TEXT(MSG_LCD_SOFT_ENDSTOPS));
  665. }
  666. #elif HAS_BED_PROBE
  667. // only change probe.offset.z
  668. probe.offset.z += diff;
  669. if (direction < 0 && current_position[axis] < Z_PROBE_OFFSET_RANGE_MIN) {
  670. current_position[axis] = Z_PROBE_OFFSET_RANGE_MIN;
  671. drawMessage(GET_TEXT(MSG_LCD_SOFT_ENDSTOPS));
  672. }
  673. else if (direction > 0 && current_position[axis] > Z_PROBE_OFFSET_RANGE_MAX) {
  674. current_position[axis] = Z_PROBE_OFFSET_RANGE_MAX;
  675. drawMessage(GET_TEXT(MSG_LCD_SOFT_ENDSTOPS));
  676. }
  677. else {
  678. drawMessage(""); // clear the error
  679. }
  680. drawAxisValue(axis);
  681. #endif
  682. return;
  683. }
  684. if (!ui.manual_move.processing) {
  685. // Get motion limit from software endstops, if any
  686. float min, max;
  687. soft_endstop.get_manual_axis_limits(axis, min, max);
  688. // Delta limits XY based on the current offset from center
  689. // This assumes the center is 0,0
  690. #if ENABLED(DELTA)
  691. if (axis != Z_AXIS && axis != E_AXIS) {
  692. max = SQRT(sq((float)(DELTA_PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis
  693. min = -max;
  694. }
  695. #endif
  696. // Get the new position
  697. #if IS_KINEMATIC
  698. ui.manual_move.offset += diff;
  699. if (direction < 0)
  700. NOLESS(ui.manual_move.offset, min - current_position[axis]);
  701. else
  702. NOMORE(ui.manual_move.offset, max - current_position[axis]);
  703. #else
  704. current_position[axis] += diff;
  705. if (direction < 0 && current_position[axis] < min) {
  706. current_position[axis] = min;
  707. drawMessage(GET_TEXT(MSG_LCD_SOFT_ENDSTOPS));
  708. }
  709. else if (direction > 0 && current_position[axis] > max) {
  710. current_position[axis] = max;
  711. drawMessage(GET_TEXT(MSG_LCD_SOFT_ENDSTOPS));
  712. }
  713. else {
  714. drawMessage(""); // clear the error
  715. }
  716. #endif
  717. ui.manual_move.soon(axis
  718. #if MULTI_MANUAL
  719. , motionAxisState.e_selection
  720. #endif
  721. );
  722. }
  723. drawAxisValue(axis);
  724. }
  725. static void e_plus() {
  726. moveAxis(E_AXIS, 1);
  727. }
  728. static void e_minus() {
  729. moveAxis(E_AXIS, -1);
  730. }
  731. static void x_minus() {
  732. moveAxis(X_AXIS, -1);
  733. }
  734. static void x_plus() {
  735. moveAxis(X_AXIS, 1);
  736. }
  737. static void y_plus() {
  738. moveAxis(Y_AXIS, 1);
  739. }
  740. static void y_minus() {
  741. moveAxis(Y_AXIS, -1);
  742. }
  743. static void z_plus() {
  744. moveAxis(Z_AXIS, 1);
  745. }
  746. static void z_minus() {
  747. moveAxis(Z_AXIS, -1);
  748. }
  749. static void e_select() {
  750. motionAxisState.e_selection++;
  751. if (motionAxisState.e_selection >= EXTRUDERS) {
  752. motionAxisState.e_selection = 0;
  753. }
  754. quick_feedback();
  755. drawCurESelection();
  756. drawAxisValue(E_AXIS);
  757. }
  758. static void do_home() {
  759. quick_feedback();
  760. drawMessage(GET_TEXT(MSG_LEVEL_BED_HOMING));
  761. queue.inject_P(G28_STR);
  762. // Disable touch until home is done
  763. TERN_(HAS_TFT_XPT2046, touch.disable());
  764. drawAxisValue(E_AXIS);
  765. drawAxisValue(X_AXIS);
  766. drawAxisValue(Y_AXIS);
  767. drawAxisValue(Z_AXIS);
  768. }
  769. static void step_size() {
  770. motionAxisState.currentStepSize = motionAxisState.currentStepSize / 10.0;
  771. if (motionAxisState.currentStepSize < 0.0015) motionAxisState.currentStepSize = 10.0;
  772. quick_feedback();
  773. drawCurStepValue();
  774. }
  775. #if HAS_BED_PROBE
  776. static void z_select() {
  777. motionAxisState.z_selection *= -1;
  778. quick_feedback();
  779. drawCurZSelection();
  780. drawAxisValue(Z_AXIS);
  781. }
  782. #endif
  783. static void disable_steppers() {
  784. quick_feedback();
  785. queue.inject_P(PSTR("M84"));
  786. }
  787. static void drawBtn(int x, int y, const char* label, int32_t data, MarlinImage img, uint16_t bgColor, bool enabled = true) {
  788. uint16_t width = Images[imgBtn52Rounded].width;
  789. uint16_t height = Images[imgBtn52Rounded].height;
  790. if (!enabled) bgColor = COLOR_CONTROL_DISABLED;
  791. tft.canvas(x, y, width, height);
  792. tft.set_background(COLOR_BACKGROUND);
  793. tft.add_image(0, 0, imgBtn52Rounded, bgColor, COLOR_BACKGROUND, COLOR_DARKGREY);
  794. // TODO: Make an add_text() taking a font arg
  795. if (label != NULL) {
  796. tft_string.set(label);
  797. tft_string.trim();
  798. tft.add_text(tft_string.center(width), height / 2 - tft_string.font_height() / 2, bgColor, tft_string);
  799. }
  800. else {
  801. tft.add_image(0, 0, img, bgColor, COLOR_BACKGROUND, COLOR_DARKGREY);
  802. }
  803. TERN_(HAS_TFT_XPT2046, if (enabled) touch.add_control(BUTTON, x, y, width, height, data));
  804. }
  805. void MarlinUI::move_axis_screen() {
  806. // Reset
  807. defer_status_screen(true);
  808. motionAxisState.blocked = false;
  809. TERN_(HAS_TFT_XPT2046, touch.enable());
  810. ui.clear_lcd();
  811. TERN_(TOUCH_SCREEN, touch.clear());
  812. const bool busy = printingIsActive();
  813. // if we have baby step and we are printing, select baby step
  814. if (busy && ENABLED(BABYSTEP_ZPROBE_OFFSET)) motionAxisState.z_selection = Z_SELECTION_Z_PROBE;
  815. // ROW 1 -> E- Y- CurY Z+
  816. int x = X_MARGIN, y = Y_MARGIN, spacing = 0;
  817. drawBtn(x, y, "E+", (int32_t)e_plus, imgUp, E_BTN_COLOR, !busy);
  818. spacing = (TFT_WIDTH - X_MARGIN * 2 - 3 * BTN_WIDTH) / 2;
  819. x += BTN_WIDTH + spacing;
  820. drawBtn(x, y, "Y+", (int32_t)y_plus, imgUp, Y_BTN_COLOR, !busy);
  821. // Cur Y
  822. x += BTN_WIDTH;
  823. motionAxisState.yValuePos.x = x + 2;
  824. motionAxisState.yValuePos.y = y;
  825. drawAxisValue(Y_AXIS);
  826. x += spacing;
  827. drawBtn(x, y, "Z+", (int32_t)z_plus, imgUp, Z_BTN_COLOR, !busy || ENABLED(BABYSTEP_ZPROBE_OFFSET)); //only enabled when not busy or have baby step
  828. // ROW 2 -> "Ex" X- HOME X+ "Z"
  829. y += BTN_HEIGHT + (TFT_HEIGHT - Y_MARGIN * 2 - 4 * BTN_HEIGHT) / 3;
  830. x = X_MARGIN;
  831. spacing = (TFT_WIDTH - X_MARGIN * 2 - 5 * BTN_WIDTH) / 4;
  832. motionAxisState.eNamePos.x = x;
  833. motionAxisState.eNamePos.y = y;
  834. drawCurESelection();
  835. TERN_(HAS_TFT_XPT2046, if (!busy) touch.add_control(BUTTON, x, y, BTN_WIDTH, BTN_HEIGHT, (int32_t)e_select));
  836. x += BTN_WIDTH + spacing;
  837. drawBtn(x, y, "X-", (int32_t)x_minus, imgLeft, X_BTN_COLOR, !busy);
  838. x += BTN_WIDTH + spacing; //imgHome is 64x64
  839. TERN_(HAS_TFT_XPT2046, add_control(TFT_WIDTH / 2 - Images[imgHome].width / 2, y - (Images[imgHome].width - BTN_HEIGHT) / 2, BUTTON, (int32_t)do_home, imgHome, !busy));
  840. x += BTN_WIDTH + spacing;
  841. uint16_t xplus_x = x;
  842. drawBtn(x, y, "X+", (int32_t)x_plus, imgRight, X_BTN_COLOR, !busy);
  843. x += BTN_WIDTH + spacing;
  844. motionAxisState.zTypePos.x = x;
  845. motionAxisState.zTypePos.y = y;
  846. drawCurZSelection();
  847. #if HAS_BED_PROBE
  848. if (!busy) touch.add_control(BUTTON, x, y, BTN_WIDTH, 34 * 2, (int32_t)z_select);
  849. #endif
  850. // ROW 3 -> E- CurX Y- Z-
  851. y += BTN_HEIGHT + (TFT_HEIGHT - Y_MARGIN * 2 - 4 * BTN_HEIGHT) / 3;
  852. x = X_MARGIN;
  853. spacing = (TFT_WIDTH - X_MARGIN * 2 - 3 * BTN_WIDTH) / 2;
  854. drawBtn(x, y, "E-", (int32_t)e_minus, imgDown, E_BTN_COLOR, !busy);
  855. // Cur E
  856. motionAxisState.eValuePos.x = x;
  857. motionAxisState.eValuePos.y = y + BTN_HEIGHT + 2;
  858. drawAxisValue(E_AXIS);
  859. // Cur X
  860. motionAxisState.xValuePos.x = BTN_WIDTH + (TFT_WIDTH - X_MARGIN * 2 - 5 * BTN_WIDTH) / 4; //X- pos
  861. motionAxisState.xValuePos.y = y - 10;
  862. drawAxisValue(X_AXIS);
  863. x += BTN_WIDTH + spacing;
  864. drawBtn(x, y, "Y-", (int32_t)y_minus, imgDown, Y_BTN_COLOR, !busy);
  865. x += BTN_WIDTH + spacing;
  866. drawBtn(x, y, "Z-", (int32_t)z_minus, imgDown, Z_BTN_COLOR, !busy || ENABLED(BABYSTEP_ZPROBE_OFFSET)); //only enabled when not busy or have baby step
  867. // Cur Z
  868. motionAxisState.zValuePos.x = x;
  869. motionAxisState.zValuePos.y = y + BTN_HEIGHT + 2;
  870. drawAxisValue(Z_AXIS);
  871. // ROW 4 -> step_size disable steppers back
  872. y = TFT_HEIGHT - Y_MARGIN - 32; //
  873. x = TFT_WIDTH / 2 - CUR_STEP_VALUE_WIDTH / 2;
  874. motionAxisState.stepValuePos.x = x;
  875. motionAxisState.stepValuePos.y = y;
  876. if (!busy) {
  877. drawCurStepValue();
  878. TERN_(HAS_TFT_XPT2046, touch.add_control(BUTTON, motionAxisState.stepValuePos.x, motionAxisState.stepValuePos.y, CUR_STEP_VALUE_WIDTH, BTN_HEIGHT, (int32_t)step_size));
  879. }
  880. // alinged with x+
  881. drawBtn(xplus_x, TFT_HEIGHT - Y_MARGIN - BTN_HEIGHT, "off", (int32_t)disable_steppers, imgCancel, COLOR_WHITE, !busy);
  882. TERN_(HAS_TFT_XPT2046, add_control(TFT_WIDTH - X_MARGIN - BTN_WIDTH, y, BACK, imgBack));
  883. }
  884. #undef BTN_WIDTH
  885. #undef BTN_HEIGHT
  886. #endif // HAS_UI_480x320