diff --git a/src/hal/TouchInput.cpp b/src/hal/TouchInput.cpp index 9024b98..3814ea2 100644 --- a/src/hal/TouchInput.cpp +++ b/src/hal/TouchInput.cpp @@ -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); diff --git a/src/hal/TouchInput.h b/src/hal/TouchInput.h index 5f44659..600ac01 100644 --- a/src/hal/TouchInput.h +++ b/src/hal/TouchInput.h @@ -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; }; diff --git a/src/ui/LvInput.cpp b/src/ui/LvInput.cpp index a4d7a66..4d19af2 100644 --- a/src/ui/LvInput.cpp +++ b/src/ui/LvInput.cpp @@ -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) { diff --git a/src/ui/screens/LvContactsScreen.cpp b/src/ui/screens/LvContactsScreen.cpp index 4be8303..d0e4b63 100644 --- a/src/ui/screens/LvContactsScreen.cpp +++ b/src/ui/screens/LvContactsScreen.cpp @@ -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); diff --git a/src/ui/screens/LvMessagesScreen.cpp b/src/ui/screens/LvMessagesScreen.cpp index 8afb4be..b05be48 100644 --- a/src/ui/screens/LvMessagesScreen.cpp +++ b/src/ui/screens/LvMessagesScreen.cpp @@ -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); diff --git a/src/ui/screens/LvNodesScreen.cpp b/src/ui/screens/LvNodesScreen.cpp index 627c545..ee10e91 100644 --- a/src/ui/screens/LvNodesScreen.cpp +++ b/src/ui/screens/LvNodesScreen.cpp @@ -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); } diff --git a/src/ui/screens/LvNodesScreen.h b/src/ui/screens/LvNodesScreen.h index 6fe6259..9e17074 100644 --- a/src/ui/screens/LvNodesScreen.h +++ b/src/ui/screens/LvNodesScreen.h @@ -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; diff --git a/src/ui/screens/LvSettingsScreen.cpp b/src/ui/screens/LvSettingsScreen.cpp index bdc2188..35127f0 100644 --- a/src/ui/screens/LvSettingsScreen.cpp +++ b/src/ui/screens/LvSettingsScreen.cpp @@ -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);