diff --git a/Marlin/src/lcd/tft/canvas.cpp b/Marlin/src/lcd/tft/canvas.cpp index 68b6f2eea8..0f77149a5a 100644 --- a/Marlin/src/lcd/tft/canvas.cpp +++ b/Marlin/src/lcd/tft/canvas.cpp @@ -31,15 +31,15 @@ uint16_t Canvas::startLine, Canvas::endLine; uint16_t Canvas::background_color; uint16_t *Canvas::buffer = TFT::buffer; -void Canvas::instantiate(uint16_t x, uint16_t y, uint16_t width, uint16_t height) { - Canvas::width = width; - Canvas::height = height; +void Canvas::instantiate(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + width = w; + height = h; startLine = 0; endLine = 0; // The TFT handles DMA within the given canvas rectangle // so whatever is drawn will be offset on the screen by x,y. - tft.set_window(x, y, x + width - 1, y + height - 1); + tft.set_window(x, y, x + w - 1, y + h - 1); } void Canvas::next() { diff --git a/Marlin/src/lcd/tft/touch.cpp b/Marlin/src/lcd/tft/touch.cpp index fbba927a86..eb62caa273 100644 --- a/Marlin/src/lcd/tft/touch.cpp +++ b/Marlin/src/lcd/tft/touch.cpp @@ -48,7 +48,7 @@ uint16_t Touch::controls_count; millis_t Touch::next_touch_ms = 0, Touch::time_to_hold, Touch::repeat_delay, - Touch::touch_time; + Touch::nada_start_ms; TouchControlType Touch::touch_control_type = NONE; #if HAS_DISPLAY_SLEEP millis_t Touch::next_sleep_ms; // = 0 @@ -78,16 +78,17 @@ void Touch::add_control(TouchControlType type, uint16_t x, uint16_t y, uint16_t } void Touch::idle() { - int16_t _x, _y; - if (!enabled) return; // Return if Touch::idle is called within the same millisecond const millis_t now = millis(); - if (now <= next_touch_ms) return; + if (now == next_touch_ms) return; next_touch_ms = now; + // Get the point and if the screen is touched do more + int16_t _x, _y; if (get_point(&_x, &_y)) { + #if HAS_RESUME_CONTINUE // UI is waiting for a click anywhere? if (wait_for_user) { @@ -100,18 +101,27 @@ void Touch::idle() { ui.reset_status_timeout(now); - if (touch_time) { + // If nada_start_ms is set the touch is being held down outside of a control. + // With TOUCH_SCREEN_CALIBRATION a long hold (2.5s by default) opens to the calibration screen. + // As long as this non-action touch is still held down, return. + if (nada_start_ms) { #if ENABLED(TOUCH_SCREEN_CALIBRATION) - if (touch_control_type == NONE && ELAPSED(now, touch_time, TOUCH_SCREEN_HOLD_TO_CALIBRATE_MS) && ui.on_status_screen()) + if (touch_control_type == NONE && ELAPSED(now, nada_start_ms, TOUCH_SCREEN_HOLD_TO_CALIBRATE_MS) && ui.on_status_screen()) ui.goto_screen(touch_screen_calibration); #endif return; } + // First time touched, ignore the control for a tiny interval as a debounce if (time_to_hold == 0) time_to_hold = now + MINIMUM_HOLD_TIME; + + // For a held control ignore the continuing touch until time elapses + // to prevent spamming controls. if (PENDING(now, time_to_hold)) return; + // Was a previous point recorded? Then we are dragging, maybe in a control. if (x != 0 && y != 0) { + // If a control was set by hold() keep sliding it until its bounds are exited if (current_control) { if (WITHIN(x, current_control->x - FREE_MOVE_RANGE, current_control->x + current_control->width + FREE_MOVE_RANGE) && WITHIN(y, current_control->y - FREE_MOVE_RANGE, current_control->y + current_control->height + FREE_MOVE_RANGE)) { LIMIT(x, current_control->x, current_control->x + current_control->width); @@ -122,65 +132,102 @@ void Touch::idle() { current_control = nullptr; } else { - for (uint16_t i = 0; i < controls_count; i++) { - if ((WITHIN(x, controls[i].x, controls[i].x + controls[i].width) && WITHIN(y, controls[i].y, controls[i].y + controls[i].height)) || (TERN(TOUCH_SCREEN_CALIBRATION, controls[i].type == CALIBRATE, false))) { - touch_control_type = controls[i].type; - touch(&controls[i]); + // Initiate a touch on the first control containing the touch position + // If this is a button the touch initiates the action on the button. + // TODO: Apply standard UI practice for Tap events: + // - Take a short press-and-release as a Tap. + // - If more taps occur before "tap detect time" elapses, increment taps counter. + // - When "tap detect time" elapses activate the button, sending the number of taps. + for (uint16_t i = 0; i < controls_count; ++i) { + auto &c = controls[i]; + if ((WITHIN(x, c.x, c.x + c.width) && WITHIN(y, c.y, c.y + c.height)) || TERN0(TOUCH_SCREEN_CALIBRATION, c.type == CALIBRATE)) { + touch_control_type = c.type; + touch(&c); break; } } } if (!current_control) - touch_time = now; + nada_start_ms = now; } x = _x; y = _y; } else { + // No touch is occurring. Continually reset these values: x = y = 0; current_control = nullptr; - touch_time = 0; + nada_start_ms = 0; touch_control_type = NONE; time_to_hold = 0; repeat_delay = TOUCH_REPEAT_DELAY; } } -void Touch::touch(touch_control_t *control) { +// Handle a touch first detected in a control +void Touch::touch(touch_control_t * const control) { switch (control->type) { + #if ENABLED(TOUCH_SCREEN_CALIBRATION) + // During touch calibration just intercept the whole screen case CALIBRATE: if (touch_calibration.handleTouch(x, y)) ui.refresh(); break; #endif + // A control that activates a menu item screen case MENU_SCREEN: ui.goto_screen((screenFunc_t)control->data); break; + + // Back Control case BACK: ui.goto_previous_screen(); break; + + // Set the encoder position to highlight the menu item but not activate it + case MENU_ITEM: ui.encoderPosition = control->data; ui.refresh(); break; + + // Move encoder to a menu item and simulate a click. + // Highlighted menu items have this type to indicate a touch will activate it. case MENU_CLICK: TERN_(SINGLE_TOUCH_NAVIGATION, ui.encoderPosition = control->data); - ui.lcd_clicked = true; - break; + // Effectively ignore the touch until it is released + time_to_hold = next_touch_ms + 2000; + // fall thru + + // Tap to Continue. e.g., Anywhere on the whole screen. case CLICK: ui.lcd_clicked = true; break; + + // Tap on button with 'true' selection + case CONFIRM: ui.encoderPosition = 1; ui.selection = true; ui.lcd_clicked = true; break; + // Tap on butto with 'false' selection + case CANCEL: ui.encoderPosition = 0; ui.selection = false; ui.lcd_clicked = true; break; + + // Specifically, Click to Continue #if HAS_RESUME_CONTINUE case RESUME_CONTINUE: extern bool wait_for_user; wait_for_user = false; break; #endif - case CANCEL: ui.encoderPosition = 0; ui.selection = false; ui.lcd_clicked = true; break; - case CONFIRM: ui.encoderPosition = 1; ui.selection = true; ui.lcd_clicked = true; break; - case MENU_ITEM: ui.encoderPosition = control->data; ui.refresh(); break; + + // Page Up button case PAGE_UP: encoderTopLine = encoderTopLine > LCD_HEIGHT ? encoderTopLine - LCD_HEIGHT : 0; ui.encoderPosition = ui.encoderPosition > LCD_HEIGHT ? ui.encoderPosition - LCD_HEIGHT : 0; ui.refresh(); break; + // 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.refresh(); break; + + // A slider is held until the touch ends, no repeat delay case SLIDER: hold(control); ui.encoderPosition = (x - control->x) * control->data / control->width; break; + + // Increase / Decrease controls are held with a repeat delay case INCREASE: hold(control, repeat_delay - 5); TERN(AUTO_BED_LEVELING_UBL, ui.external_control ? bedlevel.encoder_diff++ : ui.encoderPosition++, ui.encoderPosition++); break; case DECREASE: hold(control, repeat_delay - 5); TERN(AUTO_BED_LEVELING_UBL, ui.external_control ? bedlevel.encoder_diff-- : ui.encoderPosition--, ui.encoderPosition--); break; + + // Other controls behave like menu items + case HEATER: { ui.clear_for_drawing(); const int8_t heater = control->data; @@ -261,7 +308,9 @@ void Touch::touch(touch_control_t *control) { } } -void Touch::hold(touch_control_t *control, millis_t delay) { +// Set the control as "held" until the touch is released +// +void Touch::hold(touch_control_t * const control, const millis_t delay/*=0*/) { current_control = control; if (delay) { repeat_delay = _MAX(delay, uint32_t(MIN_REPEAT_DELAY)); diff --git a/Marlin/src/lcd/tft/touch.h b/Marlin/src/lcd/tft/touch.h index efbd427c31..cf52f896bc 100644 --- a/Marlin/src/lcd/tft/touch.h +++ b/Marlin/src/lcd/tft/touch.h @@ -83,16 +83,20 @@ class Touch { static touch_control_t *current_control; static uint16_t controls_count; - static millis_t next_touch_ms, time_to_hold, repeat_delay, touch_time; + static millis_t next_touch_ms, time_to_hold, repeat_delay, nada_start_ms; static TouchControlType touch_control_type; static bool get_point(int16_t * const x, int16_t * const y); - static void touch(touch_control_t *control); - static void hold(touch_control_t *control, millis_t delay=0); + + // Touch first detected in a control + static void touch(touch_control_t * const control); + + // Set the control as "held" until the touch is released + static void hold(touch_control_t * const control, const millis_t delay=0); public: static void init(); - static void reset() { controls_count = 0; touch_time = 0; current_control = nullptr; } + static void reset() { controls_count = 0; nada_start_ms = 0; current_control = nullptr; } static void clear() { controls_count = 0; } static void idle(); static bool is_clicked() {