/** * Marlin 3D Printer Firmware * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * 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 . * */ #include "../../inc/MarlinConfig.h" #if ENABLED(TOUCH_SCREEN) #include "touch.h" #include "../ultralcd.h" // for ui methods #include "../menu/menu_item.h" // for touch_screen_calibration #include "../../module/temperature.h" #include "../../module/planner.h" #if ENABLED(AUTO_BED_LEVELING_UBL) #include "../../feature/bedlevel/bedlevel.h" #endif #include "tft.h" bool Touch::enabled = true; int16_t Touch::x, Touch::y; touch_control_t Touch::controls[]; touch_control_t *Touch::current_control; uint16_t Touch::controls_count; millis_t Touch::now = 0; millis_t Touch::time_to_hold; millis_t Touch::repeat_delay; millis_t Touch::touch_time; TouchControlType Touch::touch_control_type = NONE; touch_calibration_t Touch::calibration; #if ENABLED(TOUCH_SCREEN_CALIBRATION) calibrationState Touch::calibration_state = CALIBRATION_NONE; touch_calibration_point_t Touch::calibration_points[4]; #endif #if HAS_RESUME_CONTINUE extern bool wait_for_user; #endif void Touch::init() { calibration_reset(); reset(); io.Init(); enable(); } void Touch::add_control(TouchControlType type, uint16_t x, uint16_t y, uint16_t width, uint16_t height, int32_t data) { if (controls_count == MAX_CONTROLS) return; controls[controls_count].type = type; controls[controls_count].x = x; controls[controls_count].y = y; controls[controls_count].width = width; controls[controls_count].height = height; controls[controls_count].data = data; controls_count++; } void Touch::idle() { uint16_t i; int16_t _x, _y; if (!enabled) return; if (now == millis()) return; now = millis(); if (get_point(&_x, &_y)) { #if HAS_RESUME_CONTINUE // UI is waiting for a click anywhere? if (wait_for_user) { touch_control_type = CLICK; ui.lcd_clicked = true; return; } #endif #if LCD_TIMEOUT_TO_STATUS ui.return_to_status_ms = now + LCD_TIMEOUT_TO_STATUS; #endif if (touch_time) { #if ENABLED(TOUCH_SCREEN_CALIBRATION) if (touch_control_type == NONE && ELAPSED(now, touch_time + TOUCH_SCREEN_HOLD_TO_CALIBRATE_MS) && ui.on_status_screen()) ui.goto_screen(touch_screen_calibration); #endif return; } if (time_to_hold == 0) time_to_hold = now + MINIMUM_HOLD_TIME; if (PENDING(now, time_to_hold)) return; if (x != 0 && y != 0) { 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)) { NOLESS(x, current_control->x); NOMORE(x, current_control->x + current_control->width); NOLESS(y, current_control->y); NOMORE(y, current_control->y + current_control->height); touch(current_control); } else { current_control = NULL; } } else { for (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]); break; } } } if (current_control == NULL) touch_time = now; } x = _x; y = _y; } else { x = y = 0; current_control = NULL; touch_time = 0; touch_control_type = NONE; time_to_hold = 0; repeat_delay = TOUCH_REPEAT_DELAY; } } void Touch::touch(touch_control_t *control) { switch (control->type) { #if ENABLED(TOUCH_SCREEN_CALIBRATION) case CALIBRATE: ui.refresh(); if (calibration_state < CALIBRATION_SUCCESS) { calibration_points[calibration_state].x = int16_t(control->data >> 16); calibration_points[calibration_state].y = int16_t(control->data & 0xFFFF); calibration_points[calibration_state].raw_x = x; calibration_points[calibration_state].raw_y = y; } switch (calibration_state) { case CALIBRATION_POINT_1: calibration_state = CALIBRATION_POINT_2; break; case CALIBRATION_POINT_2: calibration_state = CALIBRATION_POINT_3; break; case CALIBRATION_POINT_3: calibration_state = CALIBRATION_POINT_4; break; case CALIBRATION_POINT_4: if (validate_precision_x(0, 1) && validate_precision_x(2, 3) && validate_precision_y(0, 2) && validate_precision_y(1, 3)) { calibration_state = CALIBRATION_SUCCESS; calibration.x = ((calibration_points[2].x - calibration_points[0].x) << 17) / (calibration_points[3].raw_x + calibration_points[2].raw_x - calibration_points[1].raw_x - calibration_points[0].raw_x); calibration.y = ((calibration_points[1].y - calibration_points[0].y) << 17) / (calibration_points[3].raw_y - calibration_points[2].raw_y + calibration_points[1].raw_y - calibration_points[0].raw_y); calibration.offset_x = calibration_points[0].x - int16_t(((calibration_points[0].raw_x + calibration_points[1].raw_x) * calibration.x) >> 17); calibration.offset_y = calibration_points[0].y - int16_t(((calibration_points[0].raw_y + calibration_points[2].raw_y) * calibration.y) >> 17); calibration.orientation = TOUCH_LANDSCAPE; } else if (validate_precision_y(0, 1) && validate_precision_y(2, 3) && validate_precision_x(0, 2) && validate_precision_x(1, 3)) { calibration_state = CALIBRATION_SUCCESS; calibration.x = ((calibration_points[2].x - calibration_points[0].x) << 17) / (calibration_points[3].raw_y + calibration_points[2].raw_y - calibration_points[1].raw_y - calibration_points[0].raw_y); calibration.y = ((calibration_points[1].y - calibration_points[0].y) << 17) / (calibration_points[3].raw_x - calibration_points[2].raw_x + calibration_points[1].raw_x - calibration_points[0].raw_x); calibration.offset_x = calibration_points[0].x - int16_t(((calibration_points[0].raw_y + calibration_points[1].raw_y) * calibration.x) >> 17); calibration.offset_y = calibration_points[0].y - int16_t(((calibration_points[0].raw_x + calibration_points[2].raw_x) * calibration.y) >> 17); calibration.orientation = TOUCH_PORTRAIT; } else { calibration_state = CALIBRATION_FAIL; calibration_reset(); } if (calibration_state == CALIBRATION_SUCCESS) { SERIAL_ECHOLN("Touch screen calibration completed"); SERIAL_ECHOLNPAIR("TOUCH_CALIBRATION_X ", calibration.x); SERIAL_ECHOLNPAIR("TOUCH_CALIBRATION_Y ", calibration.y); SERIAL_ECHOLNPAIR("TOUCH_OFFSET_X ", calibration.offset_x); SERIAL_ECHOLNPAIR("TOUCH_OFFSET_Y ", calibration.offset_y); SERIAL_ECHO("TOUCH_ORIENTATION "); if (calibration.orientation == TOUCH_LANDSCAPE) SERIAL_ECHOLN("TOUCH_LANDSCAPE"); else SERIAL_ECHOLN("TOUCH_PORTRAIT"); } break; default: break; } break; #endif // TOUCH_SCREEN_CALIBRATION case MENU_SCREEN: ui.goto_screen((screenFunc_t)control->data); break; case BACK: ui.goto_previous_screen(); break; case CLICK: ui.lcd_clicked = true; break; #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; 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; 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; case SLIDER: hold(control); ui.encoderPosition = (x - control->x) * control->data / control->width; break; case INCREASE: hold(control, repeat_delay - 5); TERN(AUTO_BED_LEVELING_UBL, ui.external_control ? ubl.encoder_diff++ : ui.encoderPosition++, ui.encoderPosition++); break; case DECREASE: hold(control, repeat_delay - 5); TERN(AUTO_BED_LEVELING_UBL, ui.external_control ? ubl.encoder_diff-- : ui.encoderPosition--, ui.encoderPosition--); break; case HEATER: int8_t heater; heater = control->data; ui.clear_lcd(); if (heater >= 0) { // HotEnd #if HOTENDS == 1 MenuItem_int3::action((const char *)GET_TEXT_F(MSG_NOZZLE), &thermalManager.temp_hotend[0].target, 0, thermalManager.heater_maxtemp[0] - 15, []{ thermalManager.start_watching_hotend(0); }); #else MenuItemBase::itemIndex = heater; MenuItem_int3::action((const char *)GET_TEXT_F(MSG_NOZZLE_N), &thermalManager.temp_hotend[heater].target, 0, thermalManager.heater_maxtemp[heater] - 15, []{ thermalManager.start_watching_hotend(MenuItemBase::itemIndex); }); #endif } #if HAS_HEATED_BED else if (heater == H_BED) { MenuItem_int3::action((const char *)GET_TEXT_F(MSG_BED), &thermalManager.temp_bed.target, 0, BED_MAXTEMP - 10, thermalManager.start_watching_bed); } #endif #if HAS_HEATED_CHAMBER else if (heater == H_CHAMBER) { MenuItem_int3::action((const char *)GET_TEXT_F(MSG_CHAMBER), &thermalManager.temp_chamber.target, 0, CHAMBER_MAXTEMP - 10, thermalManager.start_watching_chamber); } #endif break; case FAN: ui.clear_lcd(); static uint8_t fan, fan_speed; fan = 0; fan_speed = thermalManager.fan_speed[fan]; MenuItem_percent::action((const char *)GET_TEXT_F(MSG_FIRST_FAN_SPEED), &fan_speed, 0, 255, []{ thermalManager.set_fan_speed(fan, fan_speed); }); break; case FEEDRATE: ui.clear_lcd(); MenuItem_int3::action((const char *)GET_TEXT_F(MSG_SPEED), &feedrate_percentage, 10, 999); break; case FLOWRATE: ui.clear_lcd(); MenuItemBase::itemIndex = control->data; #if EXTRUDERS == 1 MenuItem_int3::action((const char *)GET_TEXT_F(MSG_FLOW), &planner.flow_percentage[MenuItemBase::itemIndex], 10, 999, []{ planner.refresh_e_factor(MenuItemBase::itemIndex); }); #else MenuItem_int3::action((const char *)GET_TEXT_F(MSG_FLOW_N), &planner.flow_percentage[MenuItemBase::itemIndex], 10, 999, []{ planner.refresh_e_factor(MenuItemBase::itemIndex); }); #endif break; #if ENABLED(AUTO_BED_LEVELING_UBL) 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; default: break; } } void Touch::hold(touch_control_t *control, millis_t delay) { current_control = control; if (delay) { repeat_delay = delay > MIN_REPEAT_DELAY ? delay : MIN_REPEAT_DELAY; time_to_hold = now + repeat_delay; } ui.refresh(); } bool Touch::get_point(int16_t *x, int16_t *y) { bool is_touched = (calibration.orientation == TOUCH_PORTRAIT ? io.getRawPoint(y, x) : io.getRawPoint(x, y)); if (is_touched && calibration.orientation != TOUCH_ORIENTATION_NONE) { *x = int16_t((int32_t(*x) * calibration.x) >> 16) + calibration.offset_x; *y = int16_t((int32_t(*y) * calibration.y) >> 16) + calibration.offset_y; #if (TFT_ROTATION & TFT_ROTATE_180) *x = TFT_WIDTH - *x; *y = TFT_HEIGHT - *y; #endif } return is_touched; } Touch touch; bool MarlinUI::touch_pressed() { return touch.is_clicked(); } void add_control(uint16_t x, uint16_t y, TouchControlType control_type, int32_t data, MarlinImage image, bool is_enabled, uint16_t color_enabled, uint16_t color_disabled) { uint16_t width = Images[image].width; uint16_t height = Images[image].height; tft.canvas(x, y, width, height); tft.add_image(0, 0, image, is_enabled ? color_enabled : color_disabled); if (is_enabled) touch.add_control(control_type, x, y, width, height, data); } #endif // TOUCH_SCREEN