diff --git a/Marlin/src/lcd/marlinui.h b/Marlin/src/lcd/marlinui.h index 8ec25ab5c6..388394f8b9 100644 --- a/Marlin/src/lcd/marlinui.h +++ b/Marlin/src/lcd/marlinui.h @@ -694,7 +694,9 @@ public: // Manual Movement static ManualMove manual_move; - static bool can_show_slider() { return !external_control && currentScreen != manual_move.screen_ptr; } + static bool can_show_slider() { + return ENABLED(TFT_COLOR_UI) || (!external_control && currentScreen != manual_move.screen_ptr); + } // Select Screen (modal NO/YES style dialog) static bool selection; diff --git a/Marlin/src/lcd/menu/menu.cpp b/Marlin/src/lcd/menu/menu.cpp index a9ddd978e7..f82d2f741e 100644 --- a/Marlin/src/lcd/menu/menu.cpp +++ b/Marlin/src/lcd/menu/menu.cpp @@ -81,6 +81,13 @@ int32_t MenuEditItemBase::minEditValue, screenFunc_t MenuEditItemBase::callbackFunc; bool MenuEditItemBase::liveEdit; +#if ENABLED(TFT_COLOR_UI) + #if ENABLED(TOUCH_SCREEN) + float MenuEditItemBase::valueStep; + #endif + intptr_t MenuEditItemBase::valueToString; +#endif + //////////////////////////////////////////// //////// Menu Navigation & History ///////// //////////////////////////////////////////// @@ -134,14 +141,18 @@ void MenuEditItemBase::edit_screen(strfunc_t strfunc, loadfunc_t loadfunc) { // Going to an edit screen sets up some persistent values first void MenuEditItemBase::goto_edit_screen( - FSTR_P const el, // Edit label - void * const ev, // Edit value pointer - const int32_t minv, // Encoder minimum - const int32_t maxv, // Encoder maximum - const uint32_t ep, // Initial encoder value - const screenFunc_t cs, // MenuItem_type::draw_edit_screen => MenuEditItemBase::edit() - const screenFunc_t cb, // Callback after edit - const bool le // Flag to call cb() during editing + FSTR_P const el // Edit label + , void * const ev // Edit value pointer + , const int32_t minv // Encoder minimum + , const int32_t maxv // Encoder maximum + #if ENABLED(TFT_COLOR_UI) + OPTARG(TOUCH_SCREEN, const float step) // Smallest step + , intptr_t to_string // Value-to-string conversion function + #endif + , const uint32_t ep // Initial encoder value + , const screenFunc_t cs // MenuItem_type::draw_edit_screen => MenuEditItemBase::edit() + , const screenFunc_t cb // Callback after edit + , const bool le // Flag to call cb() during editing ) { TERN_(HAS_TOUCH_BUTTONS, ui.on_edit_screen = true); ui.screen_changed = true; @@ -151,6 +162,10 @@ void MenuEditItemBase::goto_edit_screen( editValue = ev; minEditValue = minv; maxEditValue = maxv; + #if ENABLED(TFT_COLOR_UI) + TERN_(TOUCH_SCREEN, valueStep = step); + valueToString = to_string; + #endif ui.encoderPosition = ep; ui.currentScreen = cs; callbackFunc = cb; diff --git a/Marlin/src/lcd/menu/menu.h b/Marlin/src/lcd/menu/menu.h index 7ef7894f7b..0bf90b142b 100644 --- a/Marlin/src/lcd/menu/menu.h +++ b/Marlin/src/lcd/menu/menu.h @@ -161,20 +161,30 @@ class MenuEditItemBase : public MenuItemBase { static FSTR_P editLabel; static void *editValue; static int32_t minEditValue, maxEditValue; // Encoder value range + #if ENABLED(TFT_COLOR_UI) + #if ENABLED(TOUCH_SCREEN) + static float valueStep; + #endif + static intptr_t valueToString; + #endif static screenFunc_t callbackFunc; static bool liveEdit; protected: typedef const char* (*strfunc_t)(const int32_t); typedef void (*loadfunc_t)(void *, const int32_t); static void goto_edit_screen( - FSTR_P const el, // Edit label - void * const ev, // Edit value pointer - const int32_t minv, // Encoder minimum - const int32_t maxv, // Encoder maximum - const uint32_t ep, // Initial encoder value - const screenFunc_t cs, // MenuItem_type::draw_edit_screen => MenuEditItemBase::edit() - const screenFunc_t cb, // Callback after edit - const bool le // Flag to call cb() during editing + FSTR_P const el // Edit label + , void * const ev // Edit value pointer + , const int32_t minv // Encoder minimum + , const int32_t maxv // Encoder maximum + #if ENABLED(TFT_COLOR_UI) + OPTARG(TOUCH_SCREEN, const float step) // Smallest step + , intptr_t to_string // Value-to-string conversion function + #endif + , const uint32_t ep // Initial encoder value + , const screenFunc_t cs // MenuItem_type::draw_edit_screen => MenuEditItemBase::edit() + , const screenFunc_t cb // Callback after edit + , const bool le // Flag to call cb() during editing ); static void edit_screen(strfunc_t, loadfunc_t); // Edit value handler public: @@ -192,6 +202,10 @@ class MenuEditItemBase : public MenuItemBase { // This method is for the current menu item static void draw_edit_screen(const char * const value) { draw_edit_screen(editLabel, value); } + + #if ALL(TFT_COLOR_UI, TOUCH_SCREEN) + static void put_new_value(float val); + #endif }; #if HAS_MEDIA diff --git a/Marlin/src/lcd/menu/menu_item.h b/Marlin/src/lcd/menu/menu_item.h index 8b23ab1b9c..5181b89797 100644 --- a/Marlin/src/lcd/menu/menu_item.h +++ b/Marlin/src/lcd/menu/menu_item.h @@ -32,6 +32,9 @@ #include "../../module/planner.h" #endif +#define DEBUG_OUT 1 +#include "../../core/debug_out.h" + //////////////////////////////////////////// ///////////// Base Menu Items ////////////// //////////////////////////////////////////// @@ -110,7 +113,7 @@ class TMenuEditItem : MenuEditItemBase { // Make sure minv and maxv fit within int32_t const int32_t minv = _MAX(scaleToEncoder(minValue), INT32_MIN), maxv = _MIN(scaleToEncoder(maxValue), INT32_MAX); - goto_edit_screen(fstr, ptr, minv, maxv - minv, scaleToEncoder(*ptr) - minv, + goto_edit_screen(fstr, ptr, minv, maxv - minv, NAME::scaleVal(), intptr_t(to_string), scaleToEncoder(*ptr) - minv, edit_screen, callback, live); } }; @@ -138,6 +141,7 @@ class TMenuEditItem : MenuEditItemBase { struct MenuEditItemInfo_##NAME { \ typedef TYPE type_t; \ /* scale the given value to the encoder */ \ + static float scaleVal() { return SCALE; } \ static int32_t scaleToEncoder(const type_t &value) { return value * (SCALE) ETC; } \ static type_t unscaleEncoder(const int32_t value) { return type_t(value) / (SCALE) ETC; } \ static const char* strfunc(const type_t &value) { return STRFUNC(_DOFIX(TYPE,value)); } \ diff --git a/Marlin/src/lcd/tft/tft.h b/Marlin/src/lcd/tft/tft.h index 5781115086..7f0baad95e 100644 --- a/Marlin/src/lcd/tft/tft.h +++ b/Marlin/src/lcd/tft/tft.h @@ -28,6 +28,10 @@ #include "tft_image.h" #include "../tft_io/tft_io.h" +#if ENABLED(TOUCH_SCREEN) + #include "touch.h" +#endif + #include "../../inc/MarlinConfig.h" #if ENABLED(TFT_INTERFACE_FSMC_8BIT) @@ -54,6 +58,8 @@ #error "TFT_BUFFER_WORDS can not exceed DMA_MAX_WORDS" #endif +enum BTN_STYLE : uint8_t { BTN_OUTLINE, BTN_FILLED, BTN_TOGGLE }; + class TFT { private: static TFT_String string; @@ -83,7 +89,10 @@ class TFT { static void add_image(int16_t x, int16_t y, MarlinImage image, uint16_t color_main = COLOR_WHITE, uint16_t color_background = COLOR_BACKGROUND, uint16_t color_shadow = COLOR_BLACK) { queue.add_image(x, y, image, color_main, color_background, color_shadow); } static void add_bar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color) { queue.add_bar(x, y, width, height, color); } static void add_rectangle(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color) { queue.add_rectangle(x, y, width, height, color); } - static void draw_edit_screen_buttons(); + static void draw_edit_screen_buttons(const bool mode_keypad=false); + #if ENABLED(TOUCH_SCREEN) + static void drawSimpleBtn(const char *label, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color, uint16_t colorTxt, BTN_STYLE style, bool selected, TouchControlType touchType = BUTTON, intptr_t data = 0, int32_t index = 0); + #endif }; extern TFT tft; diff --git a/Marlin/src/lcd/tft/touch.cpp b/Marlin/src/lcd/tft/touch.cpp index 661a013323..c9bca4645b 100644 --- a/Marlin/src/lcd/tft/touch.cpp +++ b/Marlin/src/lcd/tft/touch.cpp @@ -38,12 +38,16 @@ #include "tft.h" +#define DEBUG_OUT 1 +#include "../../core/debug_out.h" + Touch touch; bool Touch::enabled = true; int16_t Touch::x, Touch::y; touch_control_t Touch::controls[]; touch_control_t *Touch::current_control; +touch_event_t Touch::touch_event; uint16_t Touch::controls_count; millis_t Touch::next_touch_ms = 0, Touch::time_to_hold, @@ -62,7 +66,7 @@ void Touch::init() { enable(); } -void Touch::add_control(TouchControlType type, uint16_t x, uint16_t y, uint16_t width, uint16_t height, intptr_t data) { +void Touch::add_control(TouchControlType type, uint16_t x, uint16_t y, uint16_t width, uint16_t height, intptr_t data, int32_t index) { if (controls_count == MAX_CONTROLS) return; controls[controls_count].type = type; @@ -71,6 +75,7 @@ void Touch::add_control(TouchControlType type, uint16_t x, uint16_t y, uint16_t controls[controls_count].width = width; controls[controls_count].height = height; controls[controls_count].data = data; + controls[controls_count].index = index; controls_count++; } @@ -174,7 +179,7 @@ void Touch::touch(touch_control_t * const control) { #endif // A control that activates a menu item screen - case MENU_SCREEN: ui.goto_screen((screenFunc_t)control->data); break; + case MENU_SCREEN: ui.goto_screen(screenFunc_t(control->data)); break; // Back Control case BACK: ui.goto_previous_screen(); break; @@ -212,7 +217,7 @@ void Touch::touch(touch_control_t * const control) { // Page Down button case PAGE_DOWN: encoderTopLine = (encoderTopLine + 2 * LCD_HEIGHT < screen_items) ? encoderTopLine + LCD_HEIGHT : screen_items - LCD_HEIGHT; - ui.encoderPosition = ui.encoderPosition + LCD_HEIGHT < (uint32_t)screen_items ? ui.encoderPosition + LCD_HEIGHT : screen_items; + ui.encoderPosition = ui.encoderPosition + LCD_HEIGHT < uint32_t(screen_items) ? ui.encoderPosition + LCD_HEIGHT : screen_items; ui.refresh(); break; @@ -226,8 +231,8 @@ void Touch::touch(touch_control_t * const control) { // Other controls behave like menu items case HEATER: { - ui.clear_for_drawing(); const int8_t heater = control->data; + ui.clear_for_drawing(); switch (heater) { default: // Hotend #if HAS_HOTEND @@ -299,8 +304,18 @@ void Touch::touch(touch_control_t * const control) { case UBL: hold(control, UBL_REPEAT_DELAY); ui.encoderPosition += control->data; break; #endif + case MOVE_AXIS: + ui.goto_screen(screenFunc_t(ui.move_axis_screen)); + break; + // TODO: TOUCH could receive data to pass to the callback - case BUTTON: ((screenFunc_t)control->data)(); break; + case BUTTON: (screenFunc_t(control->data))(); break; + case CALLBACK: + DEBUG_ECHOLNPGM("Previous event value: ", touch_event.index); + touch_event.index = control->index; + DEBUG_ECHOLNPGM("Create touch event: ", touch_event.index); + (touch_handler_t(control->data))(&touch_event); + break; default: break; } diff --git a/Marlin/src/lcd/tft/touch.h b/Marlin/src/lcd/tft/touch.h index cf52f896bc..1fc19254a6 100644 --- a/Marlin/src/lcd/tft/touch.h +++ b/Marlin/src/lcd/tft/touch.h @@ -52,18 +52,25 @@ enum TouchControlType : uint16_t { FEEDRATE, FLOWRATE, UBL, STOP, - BUTTON + BUTTON, + CALLBACK }; typedef struct __attribute__((__packed__)) { TouchControlType type; - uint16_t x; - uint16_t y; - uint16_t width; - uint16_t height; + uint16_t x, y; + uint16_t width, height; intptr_t data; + int32_t index; } touch_control_t; +typedef struct { + uint16_t x, y; + int32_t index; +} touch_event_t; + +typedef void (*touch_handler_t)(touch_event_t*); + #define MAX_CONTROLS 16 #define MINIMUM_HOLD_TIME 15 #define TOUCH_REPEAT_DELAY 75 @@ -95,6 +102,7 @@ class Touch { static void hold(touch_control_t * const control, const millis_t delay=0); public: + static touch_event_t touch_event; static void init(); static void reset() { controls_count = 0; nada_start_ms = 0; current_control = nullptr; } static void clear() { controls_count = 0; } @@ -114,7 +122,7 @@ class Touch { static void sleepTimeout(); static void wakeUp(); #endif - static void add_control(TouchControlType type, uint16_t x, uint16_t y, uint16_t width, uint16_t height, intptr_t data=0); + static void add_control(TouchControlType type, uint16_t x, uint16_t y, uint16_t width, uint16_t height, intptr_t data=0, int32_t index=0); static void add_control(TouchControlType type, uint16_t x, uint16_t y, uint16_t width, uint16_t height, void (*handler)()) { add_control(type, x, y, width, height, intptr_t(handler)); } diff --git a/Marlin/src/lcd/tft/ui_color_ui.cpp b/Marlin/src/lcd/tft/ui_color_ui.cpp index 53b3b0d068..346cd8d36e 100644 --- a/Marlin/src/lcd/tft/ui_color_ui.cpp +++ b/Marlin/src/lcd/tft/ui_color_ui.cpp @@ -45,6 +45,11 @@ #include "../../feature/bedlevel/bedlevel.h" #endif +#define BTN_WIDTH 48 +#define BTN_HEIGHT 36 +#define X_MARGIN 15 +#define Y_MARGIN 11 + void MarlinUI::tft_idle() { #if ENABLED(TOUCH_SCREEN) if (TERN0(HAS_DISPLAY_SLEEP, lcd_sleep_task())) return; @@ -254,7 +259,7 @@ void MarlinUI::draw_status_screen() { // Coordinates #if ENABLED(MOVE_AXIS_SCREEN) - TERN_(TOUCH_SCREEN, touch.add_control(MENU_SCREEN, COORDINATES_X, COORDINATES_Y, COORDINATES_W, COORDINATES_H, (intptr_t) ui.move_axis_screen)); + TERN_(TOUCH_SCREEN, touch.add_control(MENU_SCREEN, COORDINATES_X, COORDINATES_Y, COORDINATES_W, COORDINATES_H, intptr_t(ui.move_axis_screen))); #endif tft.canvas(COORDINATES_X, COORDINATES_Y, COORDINATES_W, COORDINATES_H); @@ -283,7 +288,7 @@ void MarlinUI::draw_status_screen() { tft_string.set('?'); else { const float z = LOGICAL_Z_POSITION(current_position.z); - tft_string.set(ftostr52sp((int16_t)z)); + tft_string.set(ftostr52sp(int16_t(z))); tft_string.rtrim(); offset += tft_string.width(); @@ -397,12 +402,133 @@ void MarlinUI::draw_status_screen() { tft.add_text(STATUS_MESSAGE_TEXT_X, STATUS_MESSAGE_TEXT_Y, COLOR_STATUS_MESSAGE, tft_string); } +typedef const char*(*to_str_edit_t)(const int32_t); +static bool mode_keypad = false; +static int32_t keypad_value = 0, keypad_value_decimal = 0; + +#if ENABLED(TOUCH_SCREEN) + + static int stepSize = -1; + static void stepChange(touch_event_t *e) { + stepSize = e->index; + ui.refresh(); + } + + static void keypadBtnCb(touch_event_t *e) { + int32_t dig = e->index; + switch (dig) { + case -4: // negative / positive + keypad_value *= -1; + break; + case -3: // Clear all + keypad_value = 0; + keypad_value_decimal = 0; + break; + case -2: // erase + if (keypad_value_decimal == 1) { + keypad_value_decimal = 0; + } + else { + keypad_value = int(keypad_value) / 10; + keypad_value_decimal /= 10; + } + break; + case -1: // decimal + keypad_value_decimal = keypad_value_decimal == 0 ? 1 : keypad_value_decimal >= 1000000 ? 0 : keypad_value_decimal * 10; + break; + default: // add digit + int8_t neg = keypad_value < 0 ? -1 : 1; + if (dig >= 0 && keypad_value * neg < 999999 && keypad_value_decimal < 1000000) { + keypad_value = keypad_value * 10 + neg * dig; + if (keypad_value_decimal > 0) keypad_value_decimal *= 10; + } + } + ui.refresh(); + } + + static void okClicked() { + if (mode_keypad) { + float newVal = (float) keypad_value / (keypad_value_decimal?:1); + MenuEditItemBase::put_new_value(newVal); + touch_event_t te; + te.index = -3; + keypadBtnCb(&te); + } + ui.lcd_clicked = true; + } + + static void setValue(touch_event_t *e) { + ui.encoderPosition = e->index; + ui.refresh(); + } + +#endif // TOUCH_SCREEN + +void TFT::drawSimpleBtn(const char *label, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color, uint16_t colorTxt, BTN_STYLE style, bool selected, TouchControlType touchType, intptr_t data, int32_t index) { + + //if (!enabled) bgColor = COLOR_CONTROL_DISABLED; + + tft.canvas(x, y, w, h); + tft.set_background(style == BTN_TOGGLE ? COLOR_BACKGROUND : color); + if (style == BTN_OUTLINE && !selected) tft.add_bar(1, 1, w - 2, h - 2, COLOR_BACKGROUND); + if (style == BTN_TOGGLE && selected) tft.add_bar(0, h - 2, w, 2, color); + + // TODO: Make an add_text() taking a font arg + if (label) { + tft_string.set(label); + tft_string.trim(); + tft.add_text(tft_string.center(w), style == BTN_TOGGLE ? 0 : tft_string.vcenter(h), ((style == BTN_OUTLINE && selected) || style == BTN_FILLED) ? colorTxt : color, tft_string); + } + + TERN_(HAS_TFT_XPT2046, if (true/*enabled*/) touch.add_control(touchType, x, y, w, h, data, index)); +} + +#if ENABLED(TOUCH_SCREEN) + void MenuEditItemBase::put_new_value(float newDisplayVal) { + const float minDisplayVal = minEditValue / valueStep, + maxDisplayVal = maxEditValue / valueStep; + + DEBUG_ECHOLNPGM("INPUT: New value ", newDisplayVal, " Min ", minDisplayVal, " Max ", maxDisplayVal); + + NOMORE(newDisplayVal, maxDisplayVal); + NOLESS(newDisplayVal, minDisplayVal); + + const int32_t newVal = newDisplayVal * valueStep; + + DEBUG_ECHOLNPGM("After clip: New value ", newDisplayVal, " Min ", minDisplayVal, " Max ", maxDisplayVal); + DEBUG_ECHOLNPGM("After clip Encoder: New value ", newVal, " Min ", minEditValue, " Max ", maxEditValue); + + ui.encoderPosition = newVal; + // TODO: Fix junction dev (minEditVal is not 0) always adds minEditVal to input value + // TODO: Does not account for value types where there is a conversion during ftostr (i.e., percent) + } + + static void switchKeypad() { + mode_keypad = !mode_keypad; + if (!mode_keypad) { + const float newVal = float(keypad_value) / (keypad_value_decimal ?: 1); + MenuEditItemBase::put_new_value(newVal); + } + else { + touch_event_t te; + te.index = -3; + keypadBtnCb(&te); + } + ui.refresh(); + } + +#endif // TOUCH_SCREEN + // Low-level draw_edit_screen can be used to draw an edit screen from anyplace void MenuEditItemBase::draw_edit_screen(FSTR_P const ftpl, const char * const value/*=nullptr*/) { ui.encoder_direction_normal(); + //ui.defer_status_screen(); // Should already be done by MarlinUI TERN_(TOUCH_SCREEN, touch.clear()); - uint16_t line = 1; + tft.canvas(0, 0, TFT_WIDTH, TFT_HEIGHT); + tft.set_background(COLOR_BACKGROUND); + + uint16_t line = 0; menu_line(line++); tft_string.set(ftpl, itemIndex, itemStringC, itemStringF); @@ -411,53 +537,127 @@ void MenuEditItemBase::draw_edit_screen(FSTR_P const ftpl, const char * const va TERN_(AUTO_BED_LEVELING_UBL, if (ui.external_control) line++); // ftostr52() will overwrite *value so *value has to be displayed first - menu_line(line); - tft_string.set(value); - tft_string.trim(); - tft.add_text(tft_string.center(TFT_WIDTH), MENU_TEXT_Y, COLOR_MENU_VALUE, tft_string); - - #if ENABLED(AUTO_BED_LEVELING_UBL) - if (ui.external_control) { - menu_line(line - 1); - - tft_string.set(X_LBL); - tft.add_text(UBL_X_LABEL_X, MENU_TEXT_Y, COLOR_MENU_TEXT, tft_string); - tft_string.set(ftostr52(LOGICAL_X_POSITION(current_position.x))); - tft_string.trim(); - tft.add_text(UBL_X_TEXT_X, MENU_TEXT_Y, COLOR_MENU_VALUE, tft_string); - - tft_string.set(Y_LBL); - tft.add_text(UBL_Y_LABEL_X, MENU_TEXT_Y, COLOR_MENU_TEXT, tft_string); - tft_string.set(ftostr52(LOGICAL_X_POSITION(current_position.y))); - tft_string.trim(); - tft.add_text(UBL_Y_TEXT_X, MENU_TEXT_Y, COLOR_MENU_VALUE, tft_string); + if (mode_keypad) { + uint16_t rows[4] = {0}, cols[4] = {0}; + for (uint8_t i = 0; i < 4; i++) { + rows[3 - i] = i == 0 ? TFT_HEIGHT - Y_MARGIN - BTN_HEIGHT : rows[3 - i + 1] - BTN_HEIGHT - Y_MARGIN / 2; + cols[i] = i == 0 ? TFT_WIDTH/2 - BTN_WIDTH * 1.5 - X_MARGIN : cols[i - 1] + BTN_WIDTH + X_MARGIN; } - #endif - if (ui.can_show_slider() && maxEditValue > 0) { - tft.canvas((TFT_WIDTH - SLIDER_W) / 2, SLIDER_Y, SLIDER_W, 16); - tft.set_background(COLOR_BACKGROUND); + char ch[2] = {0}; + for (uint8_t i = 1; i < 10; i++) { + ch[0] = '0' + i; + tft.drawSimpleBtn(&ch[0], cols[(i - 1) % 3], rows[(i - 1) / 3], BTN_WIDTH, BTN_HEIGHT, COLOR_WHITE, COLOR_WHITE, BTN_OUTLINE,false, CALLBACK, intptr_t(keypadBtnCb), i); + } - int16_t position = (SLIDER_W - 2) * ui.encoderPosition / maxEditValue; - tft.add_bar(0, 7, 1, 2, ui.encoderPosition == 0 ? COLOR_SLIDER_INACTIVE : COLOR_SLIDER); - tft.add_bar(1, 6, position, 4, COLOR_SLIDER); - tft.add_bar(position + 1, 6, SLIDER_W - 2 - position, 4, COLOR_SLIDER_INACTIVE); - tft.add_bar(SLIDER_W - 1, 7, 1, 2, int32_t(ui.encoderPosition) == maxEditValue ? COLOR_SLIDER : COLOR_SLIDER_INACTIVE); + tft.drawSimpleBtn("+/-", cols[3], rows[2], BTN_WIDTH, BTN_HEIGHT, COLOR_WHITE,COLOR_WHITE, BTN_OUTLINE, false, CALLBACK, intptr_t(keypadBtnCb), -4); + ch[0] = 'C'; + tft.drawSimpleBtn(&ch[0], cols[3], rows[0], BTN_WIDTH, BTN_HEIGHT, COLOR_WHITE,COLOR_WHITE, BTN_OUTLINE, false, CALLBACK, intptr_t(keypadBtnCb), -3); + ch[0] = '<'; + tft.drawSimpleBtn(&ch[0], cols[3], rows[1], BTN_WIDTH, BTN_HEIGHT, COLOR_WHITE,COLOR_WHITE, BTN_OUTLINE, false, CALLBACK, intptr_t(keypadBtnCb), -2); + ch[0] = '.'; + tft.drawSimpleBtn(&ch[0], cols[0], rows[3], BTN_WIDTH, BTN_HEIGHT, COLOR_WHITE,COLOR_WHITE, BTN_OUTLINE, false, CALLBACK, intptr_t(keypadBtnCb), -1); + ch[0] = '0'; + tft.drawSimpleBtn(&ch[0], cols[1], rows[3], BTN_WIDTH, BTN_HEIGHT, COLOR_WHITE,COLOR_WHITE, BTN_OUTLINE, false, CALLBACK, intptr_t(keypadBtnCb), 0); - #if ENABLED(TOUCH_SCREEN) - tft.add_image((SLIDER_W - 8) * ui.encoderPosition / maxEditValue, 0, imgSlider, COLOR_SLIDER); - touch.add_control(SLIDER, (TFT_WIDTH - SLIDER_W) / 2, SLIDER_Y - 8, SLIDER_W, 32, maxEditValue); + menu_line(line); + tft_string.set(keypad_value < 0 ? '-' : ' '); + tft_string.add(ftostr7xrj(keypad_value, keypad_value_decimal ?: 1)); + if (keypad_value_decimal == 1) tft_string.add('.'); + tft_string.trim(); + tft.add_text(tft_string.center(TFT_WIDTH), MENU_TEXT_Y_OFFSET, COLOR_MENU_VALUE, tft_string); + } + else { + + menu_line(line); + tft_string.set(value); + tft_string.trim(); + tft.add_text(tft_string.center(TFT_WIDTH), MENU_TEXT_Y, COLOR_MENU_VALUE, tft_string); + + #if ENABLED(AUTO_BED_LEVELING_UBL) + if (ui.external_control) { // TODO: Disable keypad for UBL or check what is happening + menu_line(line - 1); + + tft_string.set(X_LBL); + tft.add_text(UBL_X_LABEL_X, MENU_TEXT_Y, COLOR_MENU_TEXT, tft_string); + tft_string.set(ftostr52(LOGICAL_X_POSITION(current_position.x))); + tft_string.trim(); + tft.add_text(UBL_X_TEXT_X, MENU_TEXT_Y, COLOR_MENU_VALUE, tft_string); + + tft_string.set(Y_LBL); + tft.add_text(UBL_Y_LABEL_X, MENU_TEXT_Y, COLOR_MENU_TEXT, tft_string); + tft_string.set(ftostr52(LOGICAL_X_POSITION(current_position.y))); + tft_string.trim(); + tft.add_text(UBL_Y_TEXT_X, MENU_TEXT_Y, COLOR_MENU_VALUE, tft_string); + } #endif + + if (ui.can_show_slider() && maxEditValue > 0) { + tft.canvas((TFT_WIDTH - SLIDER_W) / 2, SLIDER_Y, SLIDER_W, 16); + tft.set_background(COLOR_BACKGROUND); + + int16_t position = (SLIDER_W - 2) * ui.encoderPosition / maxEditValue; + tft.add_bar(0, 7, 1, 2, ui.encoderPosition == 0 ? COLOR_SLIDER_INACTIVE : COLOR_SLIDER); + tft.add_bar(1, 6, position, 4, COLOR_SLIDER); + tft.add_bar(position + 1, 6, SLIDER_W - 2 - position, 4, COLOR_SLIDER_INACTIVE); + tft.add_bar(SLIDER_W - 1, 7, 1, 2, int32_t(ui.encoderPosition) == maxEditValue ? COLOR_SLIDER : COLOR_SLIDER_INACTIVE); + + #if ENABLED(TOUCH_SCREEN) + tft.add_image((SLIDER_W - 8) * ui.encoderPosition / maxEditValue, 0, imgSlider, COLOR_SLIDER); + touch.add_control(SLIDER, (TFT_WIDTH - SLIDER_W) / 2, SLIDER_Y - 8, SLIDER_W, 32, maxEditValue); + #endif + + int w = (TFT_WIDTH - SLIDER_LENGTH) / 2 - 2; + int h = FONT_LINE_HEIGHT; + tft_string.set(shortenNum(to_str_edit_t(valueToString)(minEditValue))); + tft.canvas(0, SLIDER_Y_POSITION, w, h); + tft.set_background(COLOR_BACKGROUND); + tft.add_text(tft_string.center(w), tft_string.vcenter(h), COLOR_WHITE, tft_string, w); + touch.add_control(CALLBACK, 0, SLIDER_Y_POSITION, w, h, intptr_t(setValue), minEditValue); + + tft_string.set(shortenNum(to_str_edit_t(valueToString)(maxEditValue))); + tft.canvas(TFT_WIDTH - w, SLIDER_Y_POSITION, w, h); + tft.set_background(COLOR_BACKGROUND); + tft.add_text(tft_string.center(w), tft_string.vcenter(h), COLOR_WHITE, tft_string, w); + touch.add_control(CALLBACK, TFT_WIDTH - w, SLIDER_Y_POSITION, w, h, intptr_t(setValue), maxEditValue); + } + + int8_t stepCount = 0; + float steps[10] = {0}; + + int32_t range = maxEditValue - minEditValue; + int32_t div = 1; + + int8_t multip = 5; + + while (range / div >= 2) { + steps[stepCount++] = div; + div *= multip; + multip = multip == 5 ? 2 : 5; + } + + if (stepSize == -1) stepSize = steps[2]; + int x_pos = (TFT_WIDTH - stepCount * (BTN_WIDTH + X_MARGIN) + X_MARGIN) / 2; + + for (int i = 0; i < stepCount; i++) { + tft.drawSimpleBtn(shortenNum(to_str_edit_t(valueToString)(steps[i])), x_pos, SLIDER_Y_POSITION + 24 + Y_MARGIN, BTN_WIDTH, FONT_LINE_HEIGHT, COLOR_WHITE, COLOR_BACKGROUND, BTN_TOGGLE, stepSize == steps[i], CALLBACK, intptr_t(stepChange), int32_t(steps[i])); + x_pos += BTN_WIDTH + X_MARGIN; + } + } - tft.draw_edit_screen_buttons(); + tft.draw_edit_screen_buttons(mode_keypad); } -void TFT::draw_edit_screen_buttons() { +void TFT::draw_edit_screen_buttons(const bool mode_keypad/*=false*/) { #if ENABLED(TOUCH_SCREEN) - add_control(BUTTON_DECREASE_X, BUTTON_DECREASE_Y, DECREASE, imgDecrease); - add_control(BUTTON_INCREASE_X, BUTTON_INCREASE_Y, INCREASE, imgIncrease); - add_control(BUTTON_CLICK_X, BUTTON_CLICK_Y, CLICK, imgConfirm); + #define BUTTON_ROW_Y (TFT_HEIGHT - Y_MARGIN - BTN_HEIGHT) + drawSimpleBtn(mode_keypad ? "-/+" : "123", X_MARGIN, BUTTON_ROW_Y, BTN_WIDTH, BTN_HEIGHT, COLOR_BLACK, COLOR_WHITE, BTN_FILLED, false, CALLBACK, intptr_t(switchKeypad)); + if (!mode_keypad) { + drawSimpleBtn("-", (TFT_WIDTH - X_MARGIN) / 2 - BTN_WIDTH, BUTTON_ROW_Y, BTN_WIDTH, BTN_HEIGHT, COLOR_WHITE, COLOR_WHITE, BTN_OUTLINE, false, DECREASE, intptr_t(stepSize)); + drawSimpleBtn("+", (TFT_WIDTH + X_MARGIN) / 2, BUTTON_ROW_Y, BTN_WIDTH, BTN_HEIGHT, COLOR_WHITE, COLOR_WHITE, BTN_OUTLINE, false, INCREASE, intptr_t(stepSize)); + } + drawSimpleBtn("OK", TFT_WIDTH - X_MARGIN - BTN_WIDTH, BUTTON_ROW_Y, BTN_WIDTH, BTN_HEIGHT, COLOR_VIVID_GREEN, COLOR_BLACK, BTN_FILLED, true, BUTTON, intptr_t(okClicked)); #endif } diff --git a/Marlin/src/lcd/tft/ui_move_axis_screen_480.cpp b/Marlin/src/lcd/tft/ui_move_axis_screen_480.cpp index e769825bd4..1637841f22 100644 --- a/Marlin/src/lcd/tft/ui_move_axis_screen_480.cpp +++ b/Marlin/src/lcd/tft/ui_move_axis_screen_480.cpp @@ -207,7 +207,7 @@ void MarlinUI::move_axis_screen() { #if HAS_X_AXIS drawBtn(TFT_WIDTH / 6 - BTN_WIDTH / 2, y, "X-", x_minus, imgLeft, X_BTN_COLOR, !busy); - TERN_(TOUCH_SCREEN, add_control(TFT_WIDTH / 2 - images[imgHome].width / 2, y - (images[imgHome].width - BTN_HEIGHT) / 2, BUTTON, do_home, imgHome, !busy)); + TERN_(TOUCH_SCREEN, add_control((TFT_WIDTH - images[imgHome].width) / 2, y - (images[imgHome].width - BTN_HEIGHT) / 2, BUTTON, do_home, imgHome, !busy)); drawBtn(TFT_WIDTH * 5 / 6 - BTN_WIDTH / 2, y, "X+", x_plus, imgRight, X_BTN_COLOR, !busy); #endif @@ -229,7 +229,7 @@ void MarlinUI::move_axis_screen() { *************************************************************************/ TERN_(HAS_EXTRUDERS, drawBtn(TFT_WIDTH / 6 - BTN_WIDTH / 2, y, "E-", e_minus, imgDown, E_BTN_COLOR, !busy)); - TERN_(HAS_Y_AXIS, drawBtn(TFT_WIDTH / 2 - BTN_WIDTH / 2, y, "Y-", y_minus, imgDown, Y_BTN_COLOR, !busy)); + TERN_(HAS_Y_AXIS, drawBtn((TFT_WIDTH - BTN_WIDTH) / 2, y, "Y-", y_minus, imgDown, Y_BTN_COLOR, !busy)); TERN_(HAS_Z_AXIS, drawBtn(TFT_WIDTH * 5 / 6 - BTN_WIDTH / 2, y, "Z-", z_minus, imgDown, Z_BTN_COLOR, !busy || ENABLED(BABYSTEP_ZPROBE_OFFSET))); y += BTN_HEIGHT + 4; diff --git a/Marlin/src/libs/numtostr.cpp b/Marlin/src/libs/numtostr.cpp index 43ecf018d7..16c821bf9e 100644 --- a/Marlin/src/libs/numtostr.cpp +++ b/Marlin/src/libs/numtostr.cpp @@ -25,9 +25,7 @@ #include "../inc/MarlinConfigPre.h" #include "../core/utility.h" -#if !ARDUINO_ARCH_ESP32 - #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" -#endif +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" constexpr char DIGIT(const uint8_t n) { return '0' + n; } @@ -139,6 +137,11 @@ const char* i16tostr3rj(const int16_t x) { return &conv[5]; } +// Convert unsigned 16bit int to string 1, 12, 123 format, capped at 999 +const char* utostr3(const uint16_t x) { + return i16tostr3left(_MIN(x, 999U)); +} + // Convert unsigned 16bit int to lj string with 123 format const char* i16tostr3left(const int16_t i) { char *str = &conv[7]; @@ -386,6 +389,32 @@ inline const char* ftostrX1rj(const float f, const int index=1) { return &conv[index]; } +/** + * Convert float to string representing its absolute value with up to 8 characters including decimal dot, right aligned, example: ____5678, ____5.67, ___45.67, __345.67, _2345.67, 12345.67 + * + * IMPORTANT: Supports only floats that can be displayed by above mentioned format + * IMPORTANT: Float is represented by scientific notation using two parameters, from which float can be acquired by intVal / decimal + * @param intVal Scaled integer value with float digits + * @param decimal divisor to acquire the float, must be power of 10 + */ +const char* ftostr7xrj(int32_t intVal, int32_t decimal) { + if (intVal < 0) intVal *= -1; // Just print the absolute value + + uint32_t div = 1; // Current digit value: 1, 10, 100, 1000... + int8_t intEnd = 7; // Index of the digit out + + // Loop while there are digits or a decimal point to print + // Collect digits from right to left + while (intEnd >= 0 && (intVal >= div || decimal >= div)) { + if (decimal == div && WITHIN(intEnd, 1, 6)) conv[intEnd--] = '.'; // Decimal at the given power of 10 + conv[intEnd] = DIGIT((intVal / div) % 10); // The digit + div *= 10; + intEnd--; + } + + return &conv[intEnd + 1]; +} + // Convert unsigned float to string with _2.3 / 12.3 format const char* ftostr31rj(const float f) { return ftostrX1rj(f, 7 - 3); } @@ -455,9 +484,43 @@ const char* ftostr52sp(const float f) { return &conv[1]; } -// Convert unsigned 16bit int to string 1, 12, 123 format, capped at 999 -const char* utostr3(const uint16_t x) { - return i16tostr3left(_MIN(x, 999U)); +const char* ftostr52custom(const_float_t f) { + long i = INTFLOAT(f, 3); + if (i < 0) i = -i; + + uint8_t dig, intEnd = 7; + + if ((dig = i % 10)) { // third digit after decimal point? + conv[4] = '.'; + conv[5] = DIGIMOD(i, 100); + conv[6] = DIGIMOD(i, 10); + conv[7] = DIGIT(dig); + intEnd = 3; + } + else if ((dig = (i / 10) % 10)) { // second digit after decimal point? + conv[5] = '.'; + conv[6] = DIGIMOD(i, 100); + conv[7] = DIGIT(dig); + intEnd = 4; + } + else if ((dig = (i / 100) % 10)) { // first digit after decimal point? + conv[6] = '.'; + conv[7] = DIGIT(dig); + intEnd = 5; + } + + conv[intEnd--] = DIGIMOD(i, 1000); + conv[intEnd--] = RJDIGIT(i, 10000); + conv[intEnd] = RJDIGIT(i, 100000); + + for (; intEnd < 8; intEnd++) { + if (conv[intEnd] != ' ' && ((i % 10) == 0 || conv[intEnd] != '0')) { //todo this should be moved under a flag or sep func + if (f < 0) conv[--intEnd] = '-'; + return &conv[intEnd]; + } + } + + return &conv[0]; } // Convert float to space-padded string with 1.23, 12.34, 123.45 format @@ -485,3 +548,43 @@ const char* ftostr52sprj(const float f) { return &conv[1]; } + +const char* shortenNum(const char * convptr, const bool removeWhole0/*=true*/, const bool removeUnit/*=true*/) { + // TODO: Implementation behaves as if removeWhole0 and removeUnit were true + + bool numberFound = false; + int8_t decimalIdx = -1; + int i; + + // i = strlen ; decimalIdx = indexOf('.') + for (i = 0; convptr[i] != '\0'; i++) + if (convptr[i] == '.') decimalIdx = i; + + // Scanning backwards from the end... + uint8_t returnStart = 8, intStart = returnStart - 1; + for (--i; i >= 0; --i) { + const char c = convptr[i]; + // Skip all space, percent + if (c == ' ' || c == '%') continue; + const bool decimal_to_left = WITHIN(decimalIdx, 0, i); + // Skip '0' and '.' to the right of the decimal point + if (((c == '0' || c == '.') && !numberFound && decimal_to_left)) + continue; + // Now preserve other '0' (and '.') characters to the left + numberFound = true; + // Keep the character + conv[intStart] = c; + // A non-0 or any character right of the decimal point? + if (c != '0' || decimal_to_left) { + // Move returnStart to the latest added character + returnStart = intStart; + //if (c == '-') conv[returnStart] = '-'; // TODO: Move '-' to the (old) returnStart index + } + // Update the index for the next character + --intStart; + } + + returnStart -= (returnStart == 8); // Only 0's were found? Return "0" (conv[8] is always '\0') + + return &conv[returnStart]; +} diff --git a/Marlin/src/libs/numtostr.h b/Marlin/src/libs/numtostr.h index 16f1f72d95..bb97af307d 100644 --- a/Marlin/src/libs/numtostr.h +++ b/Marlin/src/libs/numtostr.h @@ -59,6 +59,9 @@ const char* i16tostr3rj(const int16_t x); // Convert signed int to lj string with 123 format const char* i16tostr3left(const int16_t xx); +// Convert unsigned int to string 1, 12, 123 format, capped at 999 +const char* utostr3(const uint16_t x); + // Convert signed int to rj string with _123, -123, _-12, or __-1 format const char* i16tostr4signrj(const int16_t x); @@ -119,6 +122,8 @@ const char* ftostr51sign(const float x); // Convert signed float to space-padded string with -_23.4_ format const char* ftostr52sp(const float x); +const char* ftostr52custom(const_float_t f); + // Convert signed float to string with +123.45 format const char* ftostr52sign(const float x); @@ -134,6 +139,9 @@ const char* ftostr51rj(const float x); // Convert unsigned float to string with ____5.6 / ___45.6 / __345.6 / _2345.6 / 12345.6 format const char* ftostr61rj(const float x); +// Convert usigned float to string with ____5.67, ___45.67, __345.67, _2345.67, 12345.67 format +const char* ftostr7xrj(int32_t intVal, int32_t decimal); + // Convert unsigned float to string with 1.23 format const char* ftostr32rj(const float f); @@ -160,8 +168,7 @@ FORCE_INLINE const char* ftostr3rj(const float x) { return i16tostr3rj(int16_t(x FORCE_INLINE const char* ftostr4sign(const float x) { return i16tostr4signrj(int16_t(x + (x < 0 ? -0.5f : 0.5f))); } #endif -// Convert unsigned int to string 1, 12, 123 format, capped at 999 -const char* utostr3(const uint16_t x); - // Convert signed float to space-padded string with 1.23, 12.34, 123.45 format const char* ftostr52sprj(const float f); + +const char* shortenNum(const char * convptr, const bool removeWhole0=true, const bool removeUnit=true);