Files
tactility/Drivers/lilygo-module/source/trackball.cpp
T
Ken Van Hoeylandt 50c0a14a93 New kernel drivers and more (#560)
- Added kernel base drivers for: display, pointer (touch, mouse, etc.), keyboard, adc, power supply and backlight
- Implement new kernel driver modules: `st7789-module` and `gt911-module`
- Implement ESP32 ADC "oneshot" kernel driver
- Implement ESP32  backlight kernel driver with ledc API
- Implemented `battery-sense` driver that allows for voltage measurement and creates a power supply child device
- Updated github actions
- Updated flash scripts
- Fix for esp32 legacy driver conflict with ADC
- Created separate `lilygo-tdeck` and `lilygo-tdeck-plus` devices
- Created `lilygo-module` with LilyGO kernel drivers
- Fix for intermittent errors in build related to code generation of `devicetree.c`
- `lvgl-module` can now map kernel drivers onto LVGL devices
- Created `KernelDisplayApp` as a mirror of `HalDisplayApp` (formerly `DisplayApp`)
- Removed `struct` and `enum` prefix in a lot of kernel driver cpp source files
- `lilygo-tdeck` and `lilygo-tdeck-plus` are now fully relying on kernel drivers and don't use any of the old HAL
2026-07-12 00:29:12 +02:00

200 lines
5.3 KiB
C++

#include <lilygo/drivers/trackball.h>
#include <lilygo/drivers/tdeck_trackball.h>
#include <Tactility/Assets.h>
#include <tactility/device.h>
#include <tactility/log.h>
constexpr auto* TAG = "Trackball";
namespace trackball {
static lv_indev_t* g_indev = nullptr;
static Device* g_device = nullptr;
static bool g_enabled = true;
static Mode g_mode = Mode::Encoder;
static uint8_t g_encoderSensitivity = 1;
static uint8_t g_pointerSensitivity = 10;
// Pointer mode cursor position (screen-relative)
static int32_t g_cursorX = 160;
static int32_t g_cursorY = 120;
static lv_obj_t* g_cursor = nullptr;
// Screen dimensions (T-Deck: 320x240)
static constexpr int32_t SCREEN_WIDTH = 320;
static constexpr int32_t SCREEN_HEIGHT = 240;
static constexpr int32_t CURSOR_SIZE = 16;
static inline int32_t clamp(int32_t val, int32_t minVal, int32_t maxVal) {
if (val < minVal) return minVal;
if (val > maxVal) return maxVal;
return val;
}
// Note: must be called from the LVGL thread (main thread), same as the setters below.
static void read_cb(lv_indev_t* /*indev*/, lv_indev_data_t* data) {
// Always drain accumulated movement so it doesn't jump on re-enable, but discard it while disabled.
int32_t dx = 0;
int32_t dy = 0;
tdeck_trackball_read_delta(g_device, &dx, &dy);
if (!g_enabled) {
dx = 0;
dy = 0;
}
if (g_mode == Mode::Encoder) {
int32_t ticks = (dx + dy) * static_cast<int32_t>(g_encoderSensitivity);
data->enc_diff = static_cast<int16_t>(clamp(ticks, INT16_MIN, INT16_MAX));
if (ticks != 0) {
lv_display_trigger_activity(nullptr);
}
} else {
g_cursorX = clamp(g_cursorX + dx * static_cast<int32_t>(g_pointerSensitivity), 0, SCREEN_WIDTH - CURSOR_SIZE - 1);
g_cursorY = clamp(g_cursorY + dy * static_cast<int32_t>(g_pointerSensitivity), 0, SCREEN_HEIGHT - CURSOR_SIZE - 1);
data->point.x = static_cast<int16_t>(g_cursorX);
data->point.y = static_cast<int16_t>(g_cursorY);
}
bool pressed = false;
if (g_enabled) {
tdeck_trackball_get_button_pressed(g_device, &pressed);
}
data->state = pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
if (pressed) {
lv_display_trigger_activity(nullptr);
}
}
lv_indev_t* init() {
if (g_indev != nullptr) {
LOG_W(TAG, "Already initialized");
return g_indev;
}
g_device = device_find_first_active_by_type(&TDECK_TRACKBALL_TYPE);
if (g_device == nullptr) {
LOG_E(TAG, "tdeck_trackball kernel device not found or not started");
return nullptr;
}
g_cursorX = SCREEN_WIDTH / 2;
g_cursorY = SCREEN_HEIGHT / 2;
g_indev = lv_indev_create();
if (g_indev == nullptr) {
LOG_E(TAG, "Failed to register LVGL input device");
g_device = nullptr;
return nullptr;
}
lv_indev_set_type(g_indev, LV_INDEV_TYPE_ENCODER);
lv_indev_set_read_cb(g_indev, read_cb);
LOG_I(TAG, "Initialized");
return g_indev;
}
// Create cursor for pointer mode
static void createCursor() {
if (g_cursor != nullptr || g_indev == nullptr) return;
g_cursor = lv_image_create(lv_layer_sys());
if (g_cursor != nullptr) {
lv_obj_remove_flag(g_cursor, LV_OBJ_FLAG_CLICKABLE);
lv_image_set_src(g_cursor, TT_ASSETS_UI_CURSOR);
lv_indev_set_cursor(g_indev, g_cursor);
LOG_D(TAG, "Cursor created");
}
}
// Destroy cursor when switching back to encoder mode
static void destroyCursor() {
if (g_cursor == nullptr) return;
// Delete the cursor object - this automatically detaches it from the indev
lv_obj_delete(g_cursor);
g_cursor = nullptr;
LOG_D(TAG, "Cursor destroyed");
}
void deinit() {
if (g_indev == nullptr) return;
destroyCursor();
lv_indev_delete(g_indev);
g_indev = nullptr;
g_device = nullptr;
g_mode = Mode::Encoder;
g_enabled = true;
LOG_I(TAG, "Deinitialized");
}
void setEncoderSensitivity(uint8_t sensitivity) {
if (sensitivity > 0) {
g_encoderSensitivity = sensitivity;
LOG_D(TAG, "Encoder sensitivity set to %d", sensitivity);
}
}
void setPointerSensitivity(uint8_t sensitivity) {
if (sensitivity > 0) {
g_pointerSensitivity = sensitivity;
LOG_D(TAG, "Pointer sensitivity set to %d", sensitivity);
}
}
void setEnabled(bool enabled) {
g_enabled = enabled;
if (g_cursor != nullptr) {
if (enabled) {
lv_obj_clear_flag(g_cursor, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(g_cursor, LV_OBJ_FLAG_HIDDEN);
}
}
LOG_I(TAG, "%s", enabled ? "Enabled" : "Disabled");
}
void setMode(Mode mode) {
if (g_indev == nullptr) {
LOG_W(TAG, "Cannot set mode - not initialized");
return;
}
if (g_mode == mode) {
return;
}
g_mode = mode;
if (mode == Mode::Pointer) {
lv_indev_set_type(g_indev, LV_INDEV_TYPE_POINTER);
createCursor();
if (!g_enabled && g_cursor != nullptr) {
lv_obj_add_flag(g_cursor, LV_OBJ_FLAG_HIDDEN);
}
g_cursorX = SCREEN_WIDTH / 2;
g_cursorY = SCREEN_HEIGHT / 2;
LOG_I(TAG, "Switched to Pointer mode");
} else {
destroyCursor();
lv_indev_set_type(g_indev, LV_INDEV_TYPE_ENCODER);
LOG_I(TAG, "Switched to Encoder mode");
}
}
Mode getMode() {
return g_mode;
}
}