1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
|
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "../inc/MarlinConfig.h"
#include "../module/motion.h"
#include "buttons.h"
#if HAS_BUZZER
#include "../libs/buzzer.h"
#endif
#if ENABLED(SDSUPPORT)
#include "../sd/cardreader.h"
#endif
#if ENABLED(TOUCH_SCREEN_CALIBRATION)
#include "tft_io/touch_calibration.h"
#endif
#if EITHER(HAS_LCD_MENU, ULTIPANEL_FEEDMULTIPLY)
#define HAS_ENCODER_ACTION 1
#endif
#if E_MANUAL > 1
#define MULTI_MANUAL 1
#endif
#if HAS_DISPLAY
#include "../module/printcounter.h"
#endif
#if BOTH(HAS_LCD_MENU, ADVANCED_PAUSE_FEATURE)
#include "../feature/pause.h"
#include "../module/motion.h" // for active_extruder
#endif
#if HAS_WIRED_LCD
enum LCDViewAction : uint8_t {
LCDVIEW_NONE,
LCDVIEW_REDRAW_NOW,
LCDVIEW_CALL_REDRAW_NEXT,
LCDVIEW_CLEAR_CALL_REDRAW,
LCDVIEW_CALL_NO_REDRAW
};
#if HAS_ADC_BUTTONS
uint8_t get_ADC_keyValue();
#endif
#define LCD_UPDATE_INTERVAL TERN(HAS_TOUCH_BUTTONS, 50, 100)
#if HAS_LCD_MENU
#include "lcdprint.h"
#if !HAS_GRAPHICAL_TFT
void _wrap_string(uint8_t &col, uint8_t &row, const char * const string, read_byte_cb_t cb_read_byte, const bool wordwrap=false);
inline void wrap_string_P(uint8_t &col, uint8_t &row, PGM_P const pstr, const bool wordwrap=false) { _wrap_string(col, row, pstr, read_byte_rom, wordwrap); }
inline void wrap_string(uint8_t &col, uint8_t &row, const char * const string, const bool wordwrap=false) { _wrap_string(col, row, string, read_byte_ram, wordwrap); }
#endif
typedef void (*screenFunc_t)();
typedef void (*menuAction_t)();
#if ENABLED(AUTO_BED_LEVELING_UBL)
void lcd_mesh_edit_setup(const float &initial);
float lcd_mesh_edit();
#endif
#endif // HAS_LCD_MENU
#endif // HAS_WIRED_LCD
#if HAS_MARLINUI_U8GLIB
enum MarlinFont : uint8_t {
FONT_STATUSMENU = 1,
FONT_EDIT,
FONT_MENU
};
#else
enum HD44780CharSet : uint8_t {
CHARSET_MENU,
CHARSET_INFO,
CHARSET_BOOT
};
#endif
#if PREHEAT_COUNT
typedef struct {
TERN_(HAS_HOTEND, uint16_t hotend_temp);
TERN_(HAS_HEATED_BED, uint16_t bed_temp );
TERN_(HAS_FAN, uint16_t fan_speed );
} preheat_t;
#endif
#if HAS_LCD_MENU
// Manual Movement class
class ManualMove {
private:
static AxisEnum axis;
#if MULTI_MANUAL
static int8_t e_index;
#else
static int8_t constexpr e_index = 0;
#endif
static millis_t start_time;
TERN_(IS_KINEMATIC, static xyze_pos_t all_axes_destination);
public:
static float menu_scale;
TERN_(IS_KINEMATIC, static float offset);
template <typename T>
void set_destination(const T& dest) {
#if IS_KINEMATIC
// Moves are segmented, so the entire move is not submitted at once.
// Using a separate variable prevents corrupting the in-progress move.
all_axes_destination = current_position;
all_axes_destination.set(dest);
#else
// Moves are submitted as single line to the planner using buffer_line.
current_position.set(dest);
#endif
}
#if IS_KINEMATIC
static bool processing;
#else
static bool constexpr processing = false;
#endif
static void task();
static void soon(AxisEnum axis
#if MULTI_MANUAL
, const int8_t eindex=-1
#endif
);
};
#endif
////////////////////////////////////////////
//////////// MarlinUI Singleton ////////////
////////////////////////////////////////////
class MarlinUI {
public:
MarlinUI() {
TERN_(HAS_LCD_MENU, currentScreen = status_screen);
}
#if HAS_MULTI_LANGUAGE
static uint8_t language;
static inline void set_language(const uint8_t lang) {
if (lang < NUM_LANGUAGES) {
language = lang;
return_to_status();
refresh();
}
}
#endif
#if ENABLED(SOUND_MENU_ITEM)
static bool buzzer_enabled; // Initialized by settings.load()
#else
static constexpr bool buzzer_enabled = true;
#endif
#if HAS_BUZZER
static void buzz(const long duration, const uint16_t freq);
#endif
FORCE_INLINE static void chirp() {
TERN_(HAS_CHIRP, buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ));
}
#if ENABLED(LCD_HAS_STATUS_INDICATORS)
static void update_indicators();
#endif
// LCD implementations
static void clear_lcd();
#if BOTH(HAS_LCD_MENU, TOUCH_SCREEN_CALIBRATION)
static void check_touch_calibration() {
if (touch_calibration.need_calibration()) currentScreen = touch_calibration_screen;
}
#endif
#if ENABLED(SDSUPPORT)
#define MEDIA_MENU_GATEWAY TERN(PASSWORD_ON_SD_PRINT_MENU, password.media_gatekeeper, menu_media)
static void media_changed(const uint8_t old_stat, const uint8_t stat);
#endif
#if ENABLED(DWIN_CREALITY_LCD)
static void refresh();
#else
FORCE_INLINE static void refresh() {
TERN_(HAS_WIRED_LCD, refresh(LCDVIEW_CLEAR_CALL_REDRAW));
}
#endif
#if HAS_WIRED_LCD
static bool detected();
static void init_lcd();
#else
static inline bool detected() { return true; }
static inline void init_lcd() {}
#endif
#if HAS_PRINT_PROGRESS
#if HAS_PRINT_PROGRESS_PERMYRIAD
typedef uint16_t progress_t;
#define PROGRESS_SCALE 100U
#define PROGRESS_MASK 0x7FFF
#else
typedef uint8_t progress_t;
#define PROGRESS_SCALE 1U
#define PROGRESS_MASK 0x7F
#endif
#if ENABLED(LCD_SET_PROGRESS_MANUALLY)
static progress_t progress_override;
static void set_progress(const progress_t p) { progress_override = _MIN(p, 100U * (PROGRESS_SCALE)); }
static void set_progress_done() { progress_override = (PROGRESS_MASK + 1U) + 100U * (PROGRESS_SCALE); }
static void progress_reset() { if (progress_override & (PROGRESS_MASK + 1U)) set_progress(0); }
#if ENABLED(SHOW_REMAINING_TIME)
static inline uint32_t _calculated_remaining_time() {
const duration_t elapsed = print_job_timer.duration();
const progress_t progress = _get_progress();
return elapsed.value * (100 * (PROGRESS_SCALE) - progress) / progress;
}
#if ENABLED(USE_M73_REMAINING_TIME)
static uint32_t remaining_time;
FORCE_INLINE static void set_remaining_time(const uint32_t r) { remaining_time = r; }
FORCE_INLINE static uint32_t get_remaining_time() { return remaining_time ?: _calculated_remaining_time(); }
FORCE_INLINE static void reset_remaining_time() { set_remaining_time(0); }
#else
FORCE_INLINE static uint32_t get_remaining_time() { return _calculated_remaining_time(); }
#endif
#endif
#endif
static progress_t _get_progress();
#if HAS_PRINT_PROGRESS_PERMYRIAD
FORCE_INLINE static uint16_t get_progress_permyriad() { return _get_progress(); }
#endif
static uint8_t get_progress_percent() { return uint8_t(_get_progress() / (PROGRESS_SCALE)); }
#else
static constexpr uint8_t get_progress_percent() { return 0; }
#endif
#if HAS_STATUS_MESSAGE
static char status_message[];
static uint8_t alert_level; // Higher levels block lower levels
#if ENABLED(STATUS_MESSAGE_SCROLLING)
static uint8_t status_scroll_offset;
static void advance_status_scroll();
static char* status_and_len(uint8_t &len);
#endif
static bool has_status();
static void reset_status(const bool no_welcome=false);
static void set_status(const char* const message, const bool persist=false);
static void set_status_P(PGM_P const message, const int8_t level=0);
static void status_printf_P(const uint8_t level, PGM_P const fmt, ...);
static void set_alert_status_P(PGM_P const message);
static inline void reset_alert_level() { alert_level = 0; }
#else
static constexpr bool has_status() { return false; }
static inline void reset_status(const bool=false) {}
static void set_status(const char* message, const bool=false);
static void set_status_P(PGM_P message, const int8_t=0);
static void status_printf_P(const uint8_t, PGM_P message, ...);
static inline void set_alert_status_P(PGM_P const) {}
static inline void reset_alert_level() {}
#endif
#if HAS_DISPLAY
static void init();
static void update();
static void abort_print();
static void pause_print();
static void resume_print();
#if HAS_WIRED_LCD
static millis_t next_button_update_ms;
static LCDViewAction lcdDrawUpdate;
FORCE_INLINE static bool should_draw() { return bool(lcdDrawUpdate); }
FORCE_INLINE static void refresh(const LCDViewAction type) { lcdDrawUpdate = type; }
#if ENABLED(SHOW_CUSTOM_BOOTSCREEN)
static void draw_custom_bootscreen(const uint8_t frame=0);
static void show_custom_bootscreen();
#endif
#if ENABLED(SHOW_BOOTSCREEN)
#ifndef BOOTSCREEN_TIMEOUT
#define BOOTSCREEN_TIMEOUT 2500
#endif
static void draw_marlin_bootscreen(const bool line2=false);
static void show_marlin_bootscreen();
static void show_bootscreen();
#endif
#if HAS_MARLINUI_U8GLIB
static void set_font(const MarlinFont font_nr);
#else
static void set_custom_characters(const HD44780CharSet screen_charset=CHARSET_INFO);
#if ENABLED(LCD_PROGRESS_BAR)
static millis_t progress_bar_ms; // Start time for the current progress bar cycle
static void draw_progress_bar(const uint8_t percent);
#if PROGRESS_MSG_EXPIRE > 0
static millis_t expire_status_ms; // = 0
FORCE_INLINE static void reset_progress_bar_timeout() { expire_status_ms = 0; }
#endif
#endif
#endif
static uint8_t lcd_status_update_delay;
#if HAS_LCD_CONTRAST
static int16_t contrast;
static void set_contrast(const int16_t value);
FORCE_INLINE static void refresh_contrast() { set_contrast(contrast); }
#endif
#if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
static millis_t next_filament_display;
#endif
static void quick_feedback(const bool clear_buttons=true);
#if HAS_BUZZER
static void completion_feedback(const bool good=true);
#else
static inline void completion_feedback(const bool=true) {}
#endif
#if DISABLED(LIGHTWEIGHT_UI)
static void draw_status_message(const bool blink);
#endif
#if ENABLED(ADVANCED_PAUSE_FEATURE)
static void draw_hotend_status(const uint8_t row, const uint8_t extruder);
#endif
#if HAS_TOUCH_BUTTONS
static bool on_edit_screen;
static void screen_click(const uint8_t row, const uint8_t col, const uint8_t x, const uint8_t y);
#endif
static void status_screen();
#endif
#if HAS_MARLINUI_U8GLIB
static bool drawing_screen, first_page;
#else
static constexpr bool drawing_screen = false, first_page = true;
#endif
static bool get_blink();
static void kill_screen(PGM_P const lcd_error, PGM_P const lcd_component);
static void draw_kill_screen();
#else // No LCD
static inline void init() {}
static inline void update() {}
static inline void return_to_status() {}
#endif
#if ENABLED(SDSUPPORT)
#if BOTH(SCROLL_LONG_FILENAMES, HAS_LCD_MENU)
#define MARLINUI_SCROLL_NAME 1
#endif
#if MARLINUI_SCROLL_NAME
static uint8_t filename_scroll_pos, filename_scroll_max;
#endif
static const char * scrolled_filename(CardReader &theCard, const uint8_t maxlen, uint8_t hash, const bool doScroll);
#endif
#if PREHEAT_COUNT
static preheat_t material_preset[PREHEAT_COUNT];
static PGM_P get_preheat_label(const uint8_t m);
#endif
#if HAS_LCD_MENU
#if LCD_TIMEOUT_TO_STATUS
static millis_t return_to_status_ms;
#endif
#if HAS_TOUCH_BUTTONS
static uint8_t touch_buttons;
static uint8_t repeat_delay;
#else
static constexpr uint8_t touch_buttons = 0;
#endif
#if ENABLED(ENCODER_RATE_MULTIPLIER)
static bool encoderRateMultiplierEnabled;
static millis_t lastEncoderMovementMillis;
static void enable_encoder_multiplier(const bool onoff);
#define ENCODER_RATE_MULTIPLY(F) (ui.encoderRateMultiplierEnabled = F)
#else
#define ENCODER_RATE_MULTIPLY(F) NOOP
#endif
// Manual Movement
static ManualMove manual_move;
// Select Screen (modal NO/YES style dialog)
static bool selection;
static void set_selection(const bool sel) { selection = sel; }
static bool update_selection();
static bool lcd_clicked;
static bool use_click();
static void synchronize(PGM_P const msg=nullptr);
static screenFunc_t currentScreen;
static bool screen_changed;
static void goto_screen(const screenFunc_t screen, const uint16_t encoder=0, const uint8_t top=0, const uint8_t items=0);
static void save_previous_screen();
// goto_previous_screen and go_back may also be used as menu item callbacks
static void _goto_previous_screen(TERN_(TURBO_BACK_MENU_ITEM, const bool is_back));
static inline void goto_previous_screen() { _goto_previous_screen(TERN_(TURBO_BACK_MENU_ITEM, false)); }
static inline void go_back() { _goto_previous_screen(TERN_(TURBO_BACK_MENU_ITEM, true)); }
static void return_to_status();
static inline bool on_status_screen() { return currentScreen == status_screen; }
FORCE_INLINE static void run_current_screen() { (*currentScreen)(); }
#if ENABLED(LIGHTWEIGHT_UI)
static void lcd_in_status(const bool inStatus);
#endif
FORCE_INLINE static void defer_status_screen(const bool defer=true) {
#if LCD_TIMEOUT_TO_STATUS > 0
defer_return_to_status = defer;
#else
UNUSED(defer);
#endif
}
static inline void goto_previous_screen_no_defer() {
defer_status_screen(false);
goto_previous_screen();
}
#if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
static void reselect_last_file();
#endif
#if ENABLED(AUTO_BED_LEVELING_UBL)
static void ubl_plot(const uint8_t x_plot, const uint8_t y_plot);
#endif
static void draw_select_screen_prompt(PGM_P const pref, const char * const string=nullptr, PGM_P const suff=nullptr);
#elif HAS_WIRED_LCD
static constexpr bool lcd_clicked = false;
static constexpr bool on_status_screen() { return true; }
FORCE_INLINE static void run_current_screen() { status_screen(); }
#endif
#if BOTH(HAS_LCD_MENU, ADVANCED_PAUSE_FEATURE)
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() {}
#define pause_show_message(...) _pause_show_message()
#endif
//
// EEPROM: Reset / Init / Load / Store
//
#if HAS_LCD_MENU
static void reset_settings();
#endif
#if ENABLED(EEPROM_SETTINGS)
#if HAS_LCD_MENU
static void init_eeprom();
static void load_settings();
static void store_settings();
#endif
#if DISABLED(EEPROM_AUTO_INIT)
static void eeprom_alert(const uint8_t msgid);
static inline void eeprom_alert_crc() { eeprom_alert(0); }
static inline void eeprom_alert_index() { eeprom_alert(1); }
static inline void eeprom_alert_version() { eeprom_alert(2); }
#endif
#endif
//
// Special handling if a move is underway
//
#if ANY(DELTA_CALIBRATION_MENU, DELTA_AUTO_CALIBRATION, PROBE_OFFSET_WIZARD) || (ENABLED(LCD_BED_LEVELING) && EITHER(PROBE_MANUALLY, MESH_BED_LEVELING))
#define LCD_HAS_WAIT_FOR_MOVE 1
static bool wait_for_move;
#else
static constexpr bool wait_for_move = false;
#endif
//
// Block interaction while under external control
//
#if HAS_LCD_MENU && EITHER(AUTO_BED_LEVELING_UBL, G26_MESH_VALIDATION)
static bool external_control;
FORCE_INLINE static void capture() { external_control = true; }
FORCE_INLINE static void release() { external_control = false; }
#if ENABLED(AUTO_BED_LEVELING_UBL)
static void external_encoder();
#endif
#else
static constexpr bool external_control = false;
#endif
#if HAS_ENCODER_ACTION
static volatile uint8_t buttons;
#if IS_RRW_KEYPAD
static volatile uint8_t keypad_buttons;
static bool handle_keypad();
#endif
#if HAS_SLOW_BUTTONS
static volatile uint8_t slow_buttons;
static uint8_t read_slow_buttons();
#endif
static void update_buttons();
static inline bool button_pressed() { return BUTTON_CLICK() || TERN(TOUCH_SCREEN, touch_pressed(), false); }
#if EITHER(AUTO_BED_LEVELING_UBL, G26_MESH_VALIDATION)
static void wait_for_release();
#endif
static uint32_t encoderPosition;
#define ENCODERBASE (TERN(REVERSE_ENCODER_DIRECTION, -1, +1))
#if EITHER(REVERSE_MENU_DIRECTION, REVERSE_SELECT_DIRECTION)
static int8_t encoderDirection;
#else
static constexpr int8_t encoderDirection = ENCODERBASE;
#endif
FORCE_INLINE static void encoder_direction_normal() {
#if EITHER(REVERSE_MENU_DIRECTION, REVERSE_SELECT_DIRECTION)
encoderDirection = ENCODERBASE;
#endif
}
FORCE_INLINE static void encoder_direction_menus() {
TERN_(REVERSE_MENU_DIRECTION, encoderDirection = -(ENCODERBASE));
}
FORCE_INLINE static void encoder_direction_select() {
TERN_(REVERSE_SELECT_DIRECTION, encoderDirection = -(ENCODERBASE));
}
#else
static inline void update_buttons() {}
#endif
#if ENABLED(TOUCH_SCREEN_CALIBRATION)
static void touch_calibration_screen();
#endif
#if HAS_GRAPHICAL_TFT
static void move_axis_screen();
#endif
private:
#if HAS_STATUS_MESSAGE
static void finish_status(const bool persist);
#endif
#if HAS_WIRED_LCD
#if HAS_LCD_MENU && LCD_TIMEOUT_TO_STATUS > 0
static bool defer_return_to_status;
#else
static constexpr bool defer_return_to_status = false;
#endif
static void draw_status_screen();
#if HAS_GRAPHICAL_TFT
static void tft_idle();
#if ENABLED(TOUCH_SCREEN)
static bool touch_pressed();
#endif
#endif
#endif
};
extern MarlinUI ui;
#define LCD_MESSAGEPGM_P(x) ui.set_status_P(x)
#define LCD_ALERTMESSAGEPGM_P(x) ui.set_alert_status_P(x)
#define LCD_MESSAGEPGM(x) LCD_MESSAGEPGM_P(GET_TEXT(x))
#define LCD_ALERTMESSAGEPGM(x) LCD_ALERTMESSAGEPGM_P(GET_TEXT(x))
|