mirror of
https://github.com/ratspeak/ratdeck.git
synced 2026-05-14 03:15:05 +00:00
Touch screen capability (closes #19)
This commit is contained in:
+5
-12
@@ -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);
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user