Touch screen capability (closes #19)

This commit is contained in:
DeFiDude
2026-04-03 00:06:48 -06:00
parent 48e2b98bd7
commit 0c47e2204f
8 changed files with 143 additions and 32 deletions
+5 -12
View File
@@ -29,13 +29,6 @@ bool TouchInput::begin() {
return true;
}
void TouchInput::setBorders(int16_t x_min, int16_t y_min, int16_t x_max, int16_t y_max) {
_x_min = x_min;
_y_min = y_min;
_x_max = x_max;
_y_max = y_max;
}
void TouchInput::update() {
readGT911();
}
@@ -93,14 +86,14 @@ bool TouchInput::readGT911() {
_y = TFT_HEIGHT - 1 - _y;
_touched = true;
// Remap
_x = (_x - _x_min) * 320 / (_x_max - _x_min);
_y = (_y - _y_min) * 240 / (_y_max - _y_min);
// Remap touchpad coordinates to display bounds
_x = (_x - TOUCH_X_MIN) * (TFT_WIDTH - 1) / (TOUCH_X_MAX - TOUCH_X_MIN);
_y = (_y - TOUCH_Y_MIN) * (TFT_HEIGHT - 1) / (TOUCH_Y_MAX - TOUCH_Y_MIN);
if (_x < 0) _x = 0;
if (_x > 319) _x = 319;
if (_x >= TFT_WIDTH) _x = TFT_WIDTH - 1;
if (_y < 0) _y = 0;
if (_y > 239) _y = 239;
if (_y >= TFT_HEIGHT) _y = TFT_HEIGHT - 1;
// Clear buffer status
Wire.beginTransmission(_i2cAddress);
+7 -5
View File
@@ -14,7 +14,6 @@ public:
int16_t y() const { return _y; }
void update();
void setBorders(int16_t x_min, int16_t y_min, int16_t x_max, int16_t y_max);
private:
bool readGT911();
@@ -24,10 +23,13 @@ private:
bool _touched = false;
int16_t _x = 0;
int16_t _y = 0;
int16_t _x_min = 10;
int16_t _y_min = 8;
int16_t _x_max = 313;
int16_t _y_max = 243;
// GT911 raw coordinate bounds (touchpad is slightly larger than display area).
// Calibrated on T-Deck Plus — consistent across multiple devices.
static constexpr int16_t TOUCH_X_MIN = 10;
static constexpr int16_t TOUCH_Y_MIN = 8;
static constexpr int16_t TOUCH_X_MAX = 313;
static constexpr int16_t TOUCH_Y_MAX = 243;
static TouchInput* _instance;
};
+23 -4
View File
@@ -121,14 +121,24 @@ static uint32_t s_lastKey = 0;
static lv_indev_state_t s_keyState = LV_INDEV_STATE_RELEASED;
static bool s_keyReady = false;
// Touch cursor auto-hide after 1s of no touch
static unsigned long s_lastTouchMs = 0;
static bool s_cursorVisible = false;
static void touchpad_read_cb(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) {
if (s_touch->isTouched()) {
lv_obj_clear_flag(s_cursor, LV_OBJ_FLAG_HIDDEN);
s_cursorVisible = true;
s_lastTouchMs = millis();
data->state = LV_INDEV_STATE_PR;
data->point.x = s_touch->x();
data->point.y = s_touch->y();
} else {
data->state = LV_INDEV_STATE_REL;
data->state = LV_INDEV_STATE_REL;
if (s_cursorVisible && millis() - s_lastTouchMs > 1000) {
lv_obj_add_flag(s_cursor, LV_OBJ_FLAG_HIDDEN);
s_cursorVisible = false;
}
}
}
@@ -168,12 +178,21 @@ void init(Keyboard* kb, Trackball* tb, TouchInput* touch) {
lv_indev_t* touchIndev = lv_indev_drv_register(&touchDrv);
lv_indev_set_group(touchIndev, s_group);
s_cursor = lv_img_create(lv_layer_sys());
lv_img_set_src(s_cursor, &mouse_cursor_icon);
// Touch cursor: black center with thin green ring (on-brand, minimal)
s_cursor = lv_obj_create(lv_layer_sys());
lv_obj_set_size(s_cursor, 12, 12);
lv_obj_set_style_radius(s_cursor, 6, 0);
lv_obj_set_style_bg_color(s_cursor, lv_color_hex(0x000000), 0);
lv_obj_set_style_bg_opa(s_cursor, LV_OPA_COVER, 0);
lv_obj_set_style_outline_color(s_cursor, lv_color_hex(0x00FF41), 0);
lv_obj_set_style_outline_width(s_cursor, 1, 0);
lv_obj_set_style_outline_opa(s_cursor, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(s_cursor, 0, 0);
lv_obj_clear_flag(s_cursor, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE);
lv_indev_set_cursor(touchIndev, s_cursor);
lv_obj_add_flag(s_cursor, LV_OBJ_FLAG_HIDDEN);
Serial.println("[LVGL] Input drivers registered (touch disabled)");
Serial.println("[LVGL] Input drivers registered (touch enabled)");
}
void feedKey(const KeyEvent& evt) {
+13
View File
@@ -42,7 +42,20 @@ void LvContactsScreen::createUI(lv_obj_t* parent) {
lv_obj_set_style_pad_all(row, 0, 0);
lv_obj_set_style_radius(row, 0, 0);
lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_flag(row, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_user_data(row, (void*)(intptr_t)i);
lv_obj_add_event_cb(row, [](lv_event_t* e) {
auto* self = (LvContactsScreen*)lv_event_get_user_data(e);
int poolIdx = (int)(intptr_t)lv_obj_get_user_data(lv_event_get_target(e));
int dataIdx = self->_viewportStart + poolIdx;
if (dataIdx < (int)self->_contactIndices.size() && self->_onSelect) {
self->_selectedIdx = dataIdx;
self->syncVisibleRows();
int nodeIdx = self->_contactIndices[dataIdx];
self->_onSelect(self->_am->nodes()[nodeIdx].hash.toHex());
}
}, LV_EVENT_CLICKED, this);
lv_obj_t* lbl = lv_label_create(row);
lv_obj_set_style_text_font(lbl, font, 0);
+13
View File
@@ -48,7 +48,20 @@ void LvMessagesScreen::createUI(lv_obj_t* parent) {
lv_obj_set_style_pad_all(row, 0, 0);
lv_obj_set_style_radius(row, 0, 0);
lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_flag(row, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_user_data(row, (void*)(intptr_t)i);
lv_obj_add_event_cb(row, [](lv_event_t* e) {
auto* self = (LvMessagesScreen*)lv_event_get_user_data(e);
lv_obj_t* target = lv_event_get_target(e);
int poolIdx = (int)(intptr_t)lv_obj_get_user_data(target);
int dataIdx = self->_viewportStart + poolIdx;
if (dataIdx < (int)self->_sortedPeers.size() && self->_onOpen) {
self->_selectedIdx = dataIdx;
self->syncVisibleRows();
self->_onOpen(self->_sortedPeers[dataIdx]);
}
}, LV_EVENT_CLICKED, this);
// Unread dot
lv_obj_t* dot = lv_obj_create(row);
+48 -11
View File
@@ -47,7 +47,22 @@ void LvNodesScreen::createUI(lv_obj_t* parent) {
lv_obj_set_style_pad_all(row, 0, 0);
lv_obj_set_style_radius(row, 0, 0);
lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_flag(row, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_user_data(row, (void*)(intptr_t)i);
lv_obj_add_event_cb(row, [](lv_event_t* e) {
auto* self = (LvNodesScreen*)lv_event_get_user_data(e);
int poolIdx = (int)(intptr_t)lv_obj_get_user_data(lv_event_get_target(e));
int entryIdx = self->_viewportStart + poolIdx;
if (entryIdx < self->_totalEntries) {
self->_selectedIdx = entryIdx;
self->syncVisibleRows();
int nodeIdx = self->getNodeIdxForEntry(entryIdx);
if (nodeIdx >= 0 && nodeIdx < (int)self->_am->nodes().size()) {
self->showActionMenu(nodeIdx);
}
}
}, LV_EVENT_CLICKED, this);
lv_obj_t* nameLbl = lv_label_create(row);
lv_obj_set_style_text_font(nameLbl, font, 0);
@@ -71,10 +86,11 @@ void LvNodesScreen::createUI(lv_obj_t* parent) {
updateSortOrder();
syncVisibleRows();
// --- Action modal overlay (hidden initially) ---
_overlay = lv_obj_create(parent);
// --- Action modal overlay (hidden initially, on top layer so it floats above scroll) ---
_overlay = lv_obj_create(lv_layer_top());
lv_obj_set_size(_overlay, 180, 100);
lv_obj_set_pos(_overlay, (Theme::CONTENT_W - 180) / 2, (Theme::CONTENT_H - 100) / 2);
// Center on screen (accounting for status bar offset)
lv_obj_set_pos(_overlay, (320 - 180) / 2, 20 + (Theme::CONTENT_H - 100) / 2);
lv_obj_set_style_bg_color(_overlay, lv_color_hex(0x001100), 0);
lv_obj_set_style_bg_opa(_overlay, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(_overlay, 1, 0);
@@ -90,10 +106,34 @@ void LvNodesScreen::createUI(lv_obj_t* parent) {
const char* menuText[] = {"Add Contact", "Message", "Back"};
for (int i = 0; i < 3; i++) {
_menuLabels[i] = lv_label_create(_overlay);
// Use a container for each menu item so it's tappable with a larger hit area
lv_obj_t* btn = lv_obj_create(_overlay);
lv_obj_set_size(btn, 166, 26);
lv_obj_set_style_bg_color(btn, lv_color_hex(Theme::BG), 0);
lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(btn, 0, 0);
lv_obj_set_style_pad_all(btn, 0, 0);
lv_obj_set_style_radius(btn, 3, 0);
lv_obj_clear_flag(btn, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_user_data(btn, (void*)(intptr_t)i);
lv_obj_add_event_cb(btn, [](lv_event_t* e) {
auto* self = (LvNodesScreen*)lv_event_get_user_data(e);
int idx = (int)(intptr_t)lv_obj_get_user_data(lv_event_get_target(e));
self->_menuIdx = idx;
// Simulate enter key to execute the menu action
KeyEvent tap = {};
tap.enter = true;
self->handleKey(tap);
}, LV_EVENT_CLICKED, this);
_menuLabels[i] = lv_label_create(btn);
lv_obj_set_style_text_font(_menuLabels[i], &lv_font_ratdeck_14, 0);
lv_obj_set_style_text_color(_menuLabels[i], lv_color_hex(Theme::PRIMARY), 0);
lv_label_set_text(_menuLabels[i], menuText[i]);
lv_obj_center(_menuLabels[i]);
_menuBtns[i] = btn;
}
// Nickname input widgets (hidden in menu mode)
@@ -340,7 +380,7 @@ void LvNodesScreen::showActionMenu(int nodeIdx) {
_actionState = NodeAction::ACTION_MENU;
_nicknameText = "";
if (_overlay) {
for (int i = 0; i < 3; i++) lv_obj_clear_flag(_menuLabels[i], LV_OBJ_FLAG_HIDDEN);
for (int i = 0; i < 3; i++) lv_obj_clear_flag(_menuBtns[i], LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(_nicknameBox, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(_overlay, LV_OBJ_FLAG_HIDDEN);
updateMenuSelection();
@@ -360,7 +400,7 @@ void LvNodesScreen::showNicknameInput() {
if (_am && _actionNodeIdx >= 0 && _actionNodeIdx < (int)_am->nodes().size()) {
_nicknameText = String(_am->nodes()[_actionNodeIdx].name.c_str());
}
for (int i = 0; i < 3; i++) lv_obj_add_flag(_menuLabels[i], LV_OBJ_FLAG_HIDDEN);
for (int i = 0; i < 3; i++) lv_obj_add_flag(_menuBtns[i], LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(_nicknameBox, LV_OBJ_FLAG_HIDDEN);
updateNicknameDisplay();
}
@@ -370,12 +410,9 @@ void LvNodesScreen::updateMenuSelection() {
bool sel = (i == _menuIdx);
lv_obj_set_style_text_color(_menuLabels[i], lv_color_hex(
sel ? Theme::BG : Theme::PRIMARY), 0);
lv_obj_set_style_bg_color(_menuLabels[i], lv_color_hex(
lv_obj_set_style_bg_color(_menuBtns[i], lv_color_hex(
sel ? Theme::PRIMARY : 0x001100), 0);
lv_obj_set_style_bg_opa(_menuLabels[i], LV_OPA_COVER, 0);
lv_obj_set_style_pad_left(_menuLabels[i], 8, 0);
lv_obj_set_style_pad_right(_menuLabels[i], 8, 0);
lv_obj_set_style_pad_top(_menuLabels[i], 2, 0);
lv_obj_set_style_bg_opa(_menuBtns[i], LV_OPA_COVER, 0);
lv_obj_set_style_pad_bottom(_menuLabels[i], 2, 0);
lv_obj_set_style_radius(_menuLabels[i], 3, 0);
}
+1
View File
@@ -54,6 +54,7 @@ private:
// Overlay widgets
lv_obj_t* _overlay = nullptr;
lv_obj_t* _menuLabels[3] = {};
lv_obj_t* _menuBtns[3] = {};
lv_obj_t* _nicknameBox = nullptr;
lv_obj_t* _nicknameLbl = nullptr;
lv_obj_t* _nicknameHint = nullptr;
+33
View File
@@ -740,6 +740,14 @@ void LvSettingsScreen::rebuildCategoryList() {
lv_obj_set_style_pad_all(row, 0, 0);
lv_obj_set_style_radius(row, 0, 0);
lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_user_data(row, (void*)(intptr_t)i);
lv_obj_add_event_cb(row, [](lv_event_t* e) {
auto* self = (LvSettingsScreen*)lv_event_get_user_data(e);
int idx = (int)(intptr_t)lv_obj_get_user_data(lv_event_get_target(e));
self->_categoryIdx = idx;
self->enterCategory(idx);
}, LV_EVENT_CLICKED, this);
// Category name + count
char buf[48];
@@ -787,6 +795,12 @@ void LvSettingsScreen::rebuildItemList() {
lv_obj_set_style_pad_all(headerRow, 0, 0);
lv_obj_set_style_radius(headerRow, 0, 0);
lv_obj_clear_flag(headerRow, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(headerRow, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(headerRow, [](lv_event_t* e) {
auto* self = (LvSettingsScreen*)lv_event_get_user_data(e);
self->exitToCategories();
}, LV_EVENT_CLICKED, this);
char headerBuf[48];
snprintf(headerBuf, sizeof(headerBuf), "< %s", _categories[_categoryIdx].name);
lv_obj_t* headerLbl = lv_label_create(headerRow);
@@ -809,6 +823,25 @@ void LvSettingsScreen::rebuildItemList() {
lv_obj_set_style_pad_all(row, 0, 0);
lv_obj_set_style_radius(row, 0, 0);
lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE);
if (editable) {
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_user_data(row, (void*)(intptr_t)i);
lv_obj_add_event_cb(row, [](lv_event_t* e) {
auto* self = (LvSettingsScreen*)lv_event_get_user_data(e);
int idx = (int)(intptr_t)lv_obj_get_user_data(lv_event_get_target(e));
// Cancel any in-progress edit before switching items
if (self->_editing || self->_textEditing || self->_freqEditing) {
self->_editing = false;
self->_textEditing = false;
self->_freqEditing = false;
self->_numericTyping = false;
}
self->_selectedIdx = idx;
KeyEvent tap = {};
tap.enter = true;
self->handleKey(tap);
}, LV_EVENT_CLICKED, this);
}
// Label
lv_obj_t* nameLbl = lv_label_create(row);