Trackball rewrite (#600)
- Implement generic trackball settings - Refactor T-Deck trackball driver into generic driver at `Drivers/gpio-trackball-module` - Automatically bind/unbind trackball devices with LVGL - Automatically load settings for trackball devices on boot
This commit is contained in:
committed by
GitHub
parent
de6fc3b346
commit
c729e8340f
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <new>
|
||||
|
||||
struct Device;
|
||||
|
||||
/**
|
||||
* Common driver-data wrapper attached to every LVGL display/indev created by this module: bundles
|
||||
* the kernel Device* with an optional device-type-specific context blob (LvglDisplayCtx,
|
||||
* LvglPointerCtx, LvglTrackballCtx, ...), so each device type doesn't have to store its own Device*
|
||||
* redundantly. Owns context: any resources referenced through it (buffers, cursor objects, etc.)
|
||||
* must be released by the caller before the wrapper is deleted, since context is trivially
|
||||
* destructible and only its own memory is freed here.
|
||||
*/
|
||||
struct LvglDeviceContext {
|
||||
struct Device* device = nullptr;
|
||||
void* context;
|
||||
|
||||
explicit LvglDeviceContext(void* context) : context(context) {}
|
||||
|
||||
~LvglDeviceContext() {
|
||||
if (context != nullptr) {
|
||||
::operator delete(context);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/error.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Trackball operating mode.
|
||||
*/
|
||||
enum LvglTrackballMode {
|
||||
/** Navigation via enc_diff (scroll wheel behavior). No cursor is shown. */
|
||||
LVGL_TRACKBALL_MODE_ENCODER,
|
||||
/** Mouse cursor via point.x/point.y. A cursor is shown if an image was set via
|
||||
* lvgl_trackball_set_cursor_image(). */
|
||||
LVGL_TRACKBALL_MODE_POINTER
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Runtime-adjustable trackball behavior. See lvgl_trackball_set_settings()/
|
||||
* lvgl_trackball_get_settings().
|
||||
*/
|
||||
struct LvglTrackballSettings {
|
||||
enum LvglTrackballMode mode;
|
||||
/** When false, movement and button presses are drained from the device and discarded:
|
||||
* the indev reports no movement and stays released. */
|
||||
bool enabled;
|
||||
/** Encoder mode: steps per raw trackball tick. Must be >= 1. */
|
||||
uint8_t encoder_sensitivity;
|
||||
/** Pointer mode: pixels per raw trackball tick. Must be >= 1. */
|
||||
uint8_t pointer_sensitivity;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Creates an lv_indev_t bound to the given TRACKBALL_TYPE device and registers a read
|
||||
* callback that polls the device through its TrackballApi.
|
||||
*
|
||||
* The indev starts in LVGL_TRACKBALL_MODE_ENCODER, enabled, with default sensitivities (see
|
||||
* lvgl_trackball_settings_get_default()). Call lvgl_trackball_set_settings() to change this,
|
||||
* e.g. to switch to pointer mode - each device added this way can independently run with or
|
||||
* without pointer support.
|
||||
*
|
||||
* @warning Caller must hold the LVGL lock (see lvgl_lock() in lvgl_module.h) — call this from
|
||||
* LvglModuleConfig.on_start, or after calling lvgl_lock() explicitly.
|
||||
*
|
||||
* @param[in] device a device of type TRACKBALL_TYPE
|
||||
* @param[in] display the display this indev should be associated with, or NULL to leave it unset
|
||||
* @param[out] out_indev the created indev, valid only when ERROR_NONE is returned
|
||||
* @retval ERROR_NONE on success
|
||||
* @retval ERROR_INVALID_ARGUMENT if device or out_indev is NULL, or device is not of type TRACKBALL_TYPE
|
||||
* @retval ERROR_OUT_OF_MEMORY if allocation failed
|
||||
*/
|
||||
error_t lvgl_trackball_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev);
|
||||
|
||||
/**
|
||||
* @brief Removes an indev previously created with lvgl_trackball_add(), including its cursor
|
||||
* object (if one was showing).
|
||||
* @warning Caller must hold the LVGL lock.
|
||||
*/
|
||||
void lvgl_trackball_remove(lv_indev_t* indev);
|
||||
|
||||
/**
|
||||
* @return the default (all fields explicitly set) LvglTrackballSettings: encoder mode, enabled,
|
||||
* encoder_sensitivity 1, pointer_sensitivity 10.
|
||||
*/
|
||||
struct LvglTrackballSettings lvgl_trackball_settings_get_default(void);
|
||||
|
||||
/**
|
||||
* @brief Applies settings to an indev previously created with lvgl_trackball_add(). Switching
|
||||
* `mode` shows/hides the cursor (pointer mode) and re-centers it on the indev's display.
|
||||
* @warning Caller must hold the LVGL lock.
|
||||
* @param[in] indev an indev previously created by lvgl_trackball_add()
|
||||
* @param[in] settings the settings to apply
|
||||
* @retval ERROR_NONE on success
|
||||
* @retval ERROR_INVALID_ARGUMENT if indev or settings is NULL, or either sensitivity is 0
|
||||
*/
|
||||
error_t lvgl_trackball_set_settings(lv_indev_t* indev, const struct LvglTrackballSettings* settings);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the settings currently active on indev.
|
||||
* @warning Caller must hold the LVGL lock.
|
||||
* @return true when indev is a trackball indev (out_settings is filled), false otherwise
|
||||
*/
|
||||
bool lvgl_trackball_get_settings(lv_indev_t* indev, struct LvglTrackballSettings* out_settings);
|
||||
|
||||
/**
|
||||
* @brief Sets the image shown as the mouse cursor while indev is in LVGL_TRACKBALL_MODE_POINTER.
|
||||
* lvgl-module has no bundled cursor asset (it doesn't depend on any particular asset layout), so
|
||||
* pointer mode has no visible cursor until a caller supplies one here; the indev still moves and
|
||||
* reports point.x/point.y either way. Pass NULL to remove the cursor.
|
||||
* @warning Caller must hold the LVGL lock.
|
||||
* @param[in] indev an indev previously created by lvgl_trackball_add()
|
||||
* @param[in] image_src an LVGL image source (as accepted by lv_image_set_src()), or NULL
|
||||
*/
|
||||
void lvgl_trackball_set_cursor_image(lv_indev_t* indev, const void* image_src);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,119 +0,0 @@
|
||||
#include <lvgl/devices/display.h>
|
||||
#include <lvgl/devices/keyboard.h>
|
||||
#include <lvgl/devices/pointer.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/display.h>
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
#include <tactility/drivers/pointer.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#define TAG "lvgl"
|
||||
|
||||
void lvgl_devices_attach() {
|
||||
lvgl_lock();
|
||||
|
||||
lv_disp_t* lvgl_display = NULL;
|
||||
bool display_updates_slowly = false;
|
||||
struct Device* kernel_display_device = NULL;
|
||||
device_get_first_by_type(&DISPLAY_TYPE, &kernel_display_device);
|
||||
|
||||
// Placeholder drivers (boards not yet migrated to the kernel display driver) register with a
|
||||
// NULL api: they exist so the devicetree node resolves, but have nothing for LVGL to bind to.
|
||||
if (kernel_display_device != NULL && device_get_driver(kernel_display_device)->api == NULL) {
|
||||
device_put(kernel_display_device);
|
||||
kernel_display_device = NULL;
|
||||
}
|
||||
|
||||
if (kernel_display_device != NULL) {
|
||||
uint16_t vres = display_get_resolution_y(kernel_display_device);
|
||||
enum DisplayColorFormat color_format = display_get_color_format(kernel_display_device);
|
||||
bool swap_bytes = color_format == DISPLAY_COLOR_FORMAT_RGB565_SWAPPED ||
|
||||
color_format == DISPLAY_COLOR_FORMAT_BGR565_SWAPPED ||
|
||||
color_format == DISPLAY_COLOR_FORMAT_BGR565;
|
||||
bool display_requires_full_frame = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_REQUIRES_FULL_FRAME);
|
||||
display_updates_slowly = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_SLOW_REFRESH);
|
||||
// Without CAP_SWAP_XY the driver can't rotate 90/270 in hardware (display_swap_xy() is
|
||||
// null and silently skipped by lvgl_display_apply_rotation()) - LVGL would still switch
|
||||
// its own logical w/h for those rotations, mismatching the panel's fixed physical
|
||||
// orientation (e.g. RGB/DPI panels, whose video timing is fixed at panel-init time).
|
||||
// sw_rotate makes LVGL rotate the rendered pixels in software instead, so the driver
|
||||
// itself is never asked to do something it can't.
|
||||
bool can_hw_rotate = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_SWAP_XY) &&
|
||||
display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_MIRROR);
|
||||
struct LvglDisplayConfig lvgl_display_config = {
|
||||
.buffer_height = vres > 10 ? vres / 10 : vres,
|
||||
.sw_rotate = !can_hw_rotate,
|
||||
.swap_bytes = swap_bytes,
|
||||
.force_full_frame = display_requires_full_frame
|
||||
};
|
||||
if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &lvgl_display) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_display_device->name);
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_display_device->name);
|
||||
}
|
||||
device_put(kernel_display_device);
|
||||
}
|
||||
|
||||
struct Device* kernel_pointer_device = NULL;
|
||||
device_get_first_by_type(&POINTER_TYPE, &kernel_pointer_device);
|
||||
if (kernel_pointer_device != NULL && device_get_driver(kernel_pointer_device)->api == NULL) {
|
||||
device_put(kernel_pointer_device);
|
||||
kernel_pointer_device = NULL;
|
||||
}
|
||||
if (kernel_pointer_device != NULL) {
|
||||
lv_indev_t* lvgl_pointer_device;
|
||||
if (lvgl_pointer_add(kernel_pointer_device, lvgl_display, &lvgl_pointer_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_pointer_device->name);
|
||||
// Slow panels cause taps to be missed due to the long update time, prevent that
|
||||
if (display_updates_slowly ) {
|
||||
lv_indev_set_long_press_time(lvgl_pointer_device, 2000);
|
||||
}
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVG", kernel_pointer_device->name);
|
||||
}
|
||||
device_put(kernel_pointer_device);
|
||||
}
|
||||
|
||||
struct Device* kernel_keyboard_device = NULL;
|
||||
device_get_first_by_type(&KEYBOARD_TYPE, &kernel_keyboard_device);
|
||||
lv_indev_t* lvgl_keyboard_device;
|
||||
if (kernel_keyboard_device != NULL) {
|
||||
if (lvgl_keyboard_add(kernel_keyboard_device, lvgl_display, &lvgl_keyboard_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_keyboard_device->name);
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_keyboard_device->name);
|
||||
}
|
||||
device_put(kernel_keyboard_device);
|
||||
}
|
||||
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
void lvgl_devices_detach() {
|
||||
lvgl_lock();
|
||||
|
||||
lv_indev_t* device = lv_indev_get_next(NULL);
|
||||
while (device != NULL) {
|
||||
lv_indev_type_t type = lv_indev_get_type(device);
|
||||
if (type == LV_INDEV_TYPE_POINTER) {
|
||||
lvgl_pointer_remove(device);
|
||||
} else if (type == LV_INDEV_TYPE_KEYPAD) {
|
||||
lvgl_keyboard_remove(device);
|
||||
} else {
|
||||
lv_indev_delete(device);
|
||||
}
|
||||
// Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
|
||||
device = lv_indev_get_next(NULL);
|
||||
}
|
||||
|
||||
lv_disp_t* display = lv_disp_get_next(NULL);
|
||||
while (display != NULL) {
|
||||
lvgl_display_remove(display);
|
||||
display = lv_disp_get_next(NULL);
|
||||
}
|
||||
|
||||
lvgl_unlock();
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
#include <lvgl/devices/display.h>
|
||||
#include <lvgl/devices/device_context.h>
|
||||
#include <lvgl/devices/keyboard.h>
|
||||
#include <lvgl/devices/pointer.h>
|
||||
#include <lvgl/devices/trackball.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/display.h>
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
#include <tactility/drivers/pointer.h>
|
||||
#include <tactility/drivers/trackball.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
constexpr auto* TAG = "lvgl";
|
||||
|
||||
// Boards are not expected to expose more devices of a single type than this; device_for_each_of_type()
|
||||
// callbacks run under the device ledger lock, so devices are collected here and processed afterwards.
|
||||
constexpr auto LVGL_DEVICES_MAX_PER_TYPE = 8;
|
||||
|
||||
struct LvglDeviceList {
|
||||
struct Device* devices[LVGL_DEVICES_MAX_PER_TYPE];
|
||||
size_t count;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
static bool lvgl_device_list_collect(struct Device* device, void* context) {
|
||||
struct LvglDeviceList* list = (struct LvglDeviceList*)context;
|
||||
if (list->count < LVGL_DEVICES_MAX_PER_TYPE) {
|
||||
list->devices[list->count++] = device;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void lvgl_devices_attach() {
|
||||
lvgl_lock();
|
||||
|
||||
lv_disp_t* lvgl_display = NULL;
|
||||
bool display_updates_slowly = false;
|
||||
|
||||
struct LvglDeviceList display_devices = {0};
|
||||
device_for_each_of_type(&DISPLAY_TYPE, &display_devices, lvgl_device_list_collect);
|
||||
for (size_t i = 0; i < display_devices.count; i++) {
|
||||
struct Device* kernel_display_device = display_devices.devices[i];
|
||||
|
||||
// Placeholder drivers (boards not yet migrated to the kernel display driver) register with a
|
||||
// NULL api: they exist so the devicetree node resolves, but have nothing for LVGL to bind to.
|
||||
if (device_get_driver(kernel_display_device)->api == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t vres = display_get_resolution_y(kernel_display_device);
|
||||
enum DisplayColorFormat color_format = display_get_color_format(kernel_display_device);
|
||||
bool swap_bytes = color_format == DISPLAY_COLOR_FORMAT_RGB565_SWAPPED ||
|
||||
color_format == DISPLAY_COLOR_FORMAT_BGR565_SWAPPED ||
|
||||
color_format == DISPLAY_COLOR_FORMAT_BGR565;
|
||||
bool display_requires_full_frame = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_REQUIRES_FULL_FRAME);
|
||||
// Without CAP_SWAP_XY the driver can't rotate 90/270 in hardware (display_swap_xy() is
|
||||
// null and silently skipped by lvgl_display_apply_rotation()) - LVGL would still switch
|
||||
// its own logical w/h for those rotations, mismatching the panel's fixed physical
|
||||
// orientation (e.g. RGB/DPI panels, whose video timing is fixed at panel-init time).
|
||||
// sw_rotate makes LVGL rotate the rendered pixels in software instead, so the driver
|
||||
// itself is never asked to do something it can't.
|
||||
bool can_hw_rotate = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_SWAP_XY) &&
|
||||
display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_CAP_MIRROR);
|
||||
struct LvglDisplayConfig lvgl_display_config = {
|
||||
.buffer_height = (uint16_t)(vres > 10 ? vres / 10 : vres),
|
||||
.sw_rotate = !can_hw_rotate,
|
||||
.swap_bytes = swap_bytes,
|
||||
.force_full_frame = display_requires_full_frame
|
||||
};
|
||||
lv_disp_t* added_display = NULL;
|
||||
if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &added_display) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_display_device->name);
|
||||
// Pointers/keyboards below bind to the first display bound here, matching that display's
|
||||
// refresh behavior.
|
||||
if (lvgl_display == NULL) {
|
||||
lvgl_display = added_display;
|
||||
display_updates_slowly = display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_SLOW_REFRESH);
|
||||
}
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_display_device->name);
|
||||
}
|
||||
}
|
||||
|
||||
struct LvglDeviceList pointer_devices = {0};
|
||||
device_for_each_of_type(&POINTER_TYPE, &pointer_devices, lvgl_device_list_collect);
|
||||
for (size_t i = 0; i < pointer_devices.count; i++) {
|
||||
struct Device* kernel_pointer_device = pointer_devices.devices[i];
|
||||
if (device_get_driver(kernel_pointer_device)->api == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lv_indev_t* lvgl_pointer_device;
|
||||
if (lvgl_pointer_add(kernel_pointer_device, lvgl_display, &lvgl_pointer_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_pointer_device->name);
|
||||
// Slow panels cause taps to be missed due to the long update time, prevent that
|
||||
if (display_updates_slowly ) {
|
||||
lv_indev_set_long_press_time(lvgl_pointer_device, 2000);
|
||||
}
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVG", kernel_pointer_device->name);
|
||||
}
|
||||
}
|
||||
|
||||
struct LvglDeviceList keyboard_devices = {0};
|
||||
device_for_each_of_type(&KEYBOARD_TYPE, &keyboard_devices, lvgl_device_list_collect);
|
||||
for (size_t i = 0; i < keyboard_devices.count; i++) {
|
||||
struct Device* kernel_keyboard_device = keyboard_devices.devices[i];
|
||||
lv_indev_t* lvgl_keyboard_device;
|
||||
if (lvgl_keyboard_add(kernel_keyboard_device, lvgl_display, &lvgl_keyboard_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_keyboard_device->name);
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_keyboard_device->name);
|
||||
}
|
||||
}
|
||||
|
||||
struct Device* kernel_trackball_device = NULL;
|
||||
device_get_first_by_type(&TRACKBALL_TYPE, &kernel_trackball_device);
|
||||
if (kernel_trackball_device != NULL) {
|
||||
lv_indev_t* lvgl_trackball_device;
|
||||
if (lvgl_trackball_add(kernel_trackball_device, lvgl_display, &lvgl_trackball_device) == ERROR_NONE) {
|
||||
LOG_I(TAG, "Bound %s to LVGL", kernel_trackball_device->name);
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_trackball_device->name);
|
||||
}
|
||||
device_put(kernel_trackball_device);
|
||||
}
|
||||
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
void lvgl_devices_detach() {
|
||||
lvgl_lock();
|
||||
|
||||
lv_indev_t* indev = lv_indev_get_next(NULL);
|
||||
while (indev != NULL) {
|
||||
lv_indev_type_t type = lv_indev_get_type(indev);
|
||||
if (type == LV_INDEV_TYPE_POINTER || type == LV_INDEV_TYPE_ENCODER) {
|
||||
bool handled = false;
|
||||
void* driver_data = lv_indev_get_driver_data(indev);
|
||||
if (driver_data != nullptr) {
|
||||
// Trackball is a special pointer type as it can operate as a mouse or as a device that changes widget focus.
|
||||
LvglDeviceContext* context = static_cast<LvglDeviceContext*>(driver_data);
|
||||
if (context->device != nullptr) {
|
||||
const DeviceType* device_type = device_get_type(context->device);
|
||||
if (device_type == &TRACKBALL_TYPE) {
|
||||
lvgl_trackball_remove(indev);
|
||||
handled = true;
|
||||
} else if (device_type == &POINTER_TYPE) {
|
||||
lvgl_pointer_remove(indev);
|
||||
handled = true;
|
||||
} else {
|
||||
LOG_E(TAG, "Unknown pointer device with possible memory leak of driver data");
|
||||
}
|
||||
} else {
|
||||
LOG_W(TAG, "Unknown pointer device (no data attached)");
|
||||
}
|
||||
}
|
||||
if (!handled) {
|
||||
lv_indev_delete(indev);
|
||||
}
|
||||
} else if (type == LV_INDEV_TYPE_KEYPAD) {
|
||||
lvgl_keyboard_remove(indev);
|
||||
} else {
|
||||
lv_indev_delete(indev);
|
||||
}
|
||||
// Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
|
||||
indev = lv_indev_get_next(NULL);
|
||||
}
|
||||
|
||||
lv_disp_t* display = lv_disp_get_next(NULL);
|
||||
while (display != NULL) {
|
||||
lvgl_display_remove(display);
|
||||
display = lv_disp_get_next(NULL);
|
||||
}
|
||||
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
} // extern C
|
||||
+37
-26
@@ -8,16 +8,17 @@
|
||||
#include <tactility/drivers/display.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <lvgl/devices/device_context.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <esp_heap_caps.h>
|
||||
#endif
|
||||
|
||||
#define TAG "lvgl_display"
|
||||
constexpr auto* TAG = "lvgl_display";
|
||||
|
||||
struct LvglDisplayCtx {
|
||||
struct Device* device;
|
||||
void* buf1;
|
||||
void* buf2;
|
||||
bool owns_buffers; // false when buf1/buf2 point at the device's own frame buffer(s)
|
||||
@@ -108,7 +109,9 @@ static bool lvgl_display_map_color_format(enum DisplayColorFormat in, lv_color_f
|
||||
}
|
||||
}
|
||||
|
||||
static void lvgl_display_apply_rotation(struct LvglDisplayCtx* ctx, lv_display_rotation_t rotation) {
|
||||
static void lvgl_display_apply_rotation(struct LvglDeviceContext* wrapper, lv_display_rotation_t rotation) {
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)wrapper->context;
|
||||
|
||||
// SW-rotated displays stay in their base orientation; rotation is applied per-flush instead.
|
||||
if (ctx->sw_rotate) {
|
||||
return;
|
||||
@@ -144,10 +147,10 @@ static void lvgl_display_apply_rotation(struct LvglDisplayCtx* ctx, lv_display_r
|
||||
}
|
||||
|
||||
if (ctx->has_swap_xy_cap) {
|
||||
display_swap_xy(ctx->device, swap_xy);
|
||||
display_swap_xy(wrapper->device, swap_xy);
|
||||
}
|
||||
if (ctx->has_mirror_cap) {
|
||||
display_mirror(ctx->device, mirror_x, mirror_y);
|
||||
display_mirror(wrapper->device, mirror_x, mirror_y);
|
||||
}
|
||||
if (ctx->has_set_gap_cap) {
|
||||
// set_gap() takes its (x,y) in whatever axes the panel is currently drawn with, not the
|
||||
@@ -155,14 +158,14 @@ static void lvgl_display_apply_rotation(struct LvglDisplayCtx* ctx, lv_display_r
|
||||
bool gap_axes_swapped = swap_xy != ctx->base_swap_xy;
|
||||
int32_t gap_x = gap_axes_swapped ? ctx->base_gap_y : ctx->base_gap_x;
|
||||
int32_t gap_y = gap_axes_swapped ? ctx->base_gap_x : ctx->base_gap_y;
|
||||
display_set_gap(ctx->device, gap_x, gap_y);
|
||||
display_set_gap(wrapper->device, gap_x, gap_y);
|
||||
}
|
||||
}
|
||||
|
||||
static void lvgl_display_rotation_event_cb(lv_event_t* e) {
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_event_get_user_data(e);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_event_get_user_data(e);
|
||||
lv_display_t* disp = (lv_display_t*)lv_event_get_current_target(e);
|
||||
lvgl_display_apply_rotation(ctx, lv_display_get_rotation(disp));
|
||||
lvgl_display_apply_rotation(wrapper, lv_display_get_rotation(disp));
|
||||
}
|
||||
|
||||
// Returns which of buf1/buf2 (the real frame buffers, when !owns_buffers) color_map falls inside.
|
||||
@@ -206,7 +209,8 @@ static void* lvgl_display_try_ppa_rotate(struct LvglDisplayCtx* ctx, const uint8
|
||||
}
|
||||
|
||||
static void lvgl_display_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* color_map) {
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(disp);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_display_get_driver_data(disp);
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)wrapper->context;
|
||||
bool is_i1 = lv_display_get_color_format(disp) == LV_COLOR_FORMAT_I1;
|
||||
|
||||
int32_t x1 = area->x1;
|
||||
@@ -280,8 +284,8 @@ static void lvgl_display_flush_cb(lv_display_t* disp, const lv_area_t* area, uin
|
||||
} else {
|
||||
fb_base = (uint8_t*)lvgl_display_fb_base(ctx, color_map);
|
||||
}
|
||||
uint16_t hres = display_get_resolution_x(ctx->device);
|
||||
uint16_t vres = display_get_resolution_y(ctx->device);
|
||||
uint16_t hres = display_get_resolution_x(wrapper->device);
|
||||
uint16_t vres = display_get_resolution_y(wrapper->device);
|
||||
|
||||
if (rotating) {
|
||||
// fb_base now holds the whole completed frame, but still in LVGL's *logical*
|
||||
@@ -303,13 +307,13 @@ static void lvgl_display_flush_cb(lv_display_t* disp, const lv_area_t* area, uin
|
||||
}
|
||||
}
|
||||
|
||||
display_draw_bitmap(ctx->device, 0, 0, hres, vres, fb_base);
|
||||
display_draw_bitmap(wrapper->device, 0, 0, hres, vres, fb_base);
|
||||
}
|
||||
} else if (ctx->owns_buffers) {
|
||||
// PARTIAL mode: each flush_cb call is one independent, complete tile into a buffer that
|
||||
// gets reused for the next tile, so present it immediately rather than waiting.
|
||||
// LVGL's area is inclusive; DisplayApi's draw_bitmap wants an exclusive end.
|
||||
display_draw_bitmap(ctx->device, x1, y1, x2 + 1, y2 + 1, color_map);
|
||||
display_draw_bitmap(wrapper->device, x1, y1, x2 + 1, y2 + 1, color_map);
|
||||
}
|
||||
// DisplayApi has no async completion callback, so draw_bitmap is synchronous.
|
||||
lv_display_flush_ready(disp);
|
||||
@@ -335,11 +339,17 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
uint8_t fb_count = display_get_frame_buffer_count(device);
|
||||
uint8_t bpp = lv_color_format_get_size(lv_color_format);
|
||||
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)calloc(1, sizeof(struct LvglDisplayCtx));
|
||||
struct LvglDisplayCtx* ctx = new(std::nothrow) LvglDisplayCtx();
|
||||
if (ctx == NULL) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->device = device;
|
||||
struct LvglDeviceContext* wrapper = new(std::nothrow) LvglDeviceContext(ctx);
|
||||
if (wrapper == NULL) {
|
||||
delete ctx;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
wrapper->device = device;
|
||||
|
||||
ctx->byte_swap = config->swap_bytes;
|
||||
ctx->sw_rotate = config->sw_rotate;
|
||||
// Only relevant when sw_rotate is set - lvgl_display_try_ppa_rotate() also checks
|
||||
@@ -388,7 +398,7 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
buf_size_bytes = (size_t)((hres + 7) / 8) * vres + 8;
|
||||
ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes);
|
||||
if (ctx->buf1 == NULL) {
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->owns_buffers = true;
|
||||
@@ -408,14 +418,14 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
|
||||
ctx->buf1 = lvgl_display_alloc_buffer(buf_size_bytes);
|
||||
if (ctx->buf1 == NULL) {
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
if (config->double_buffer) {
|
||||
ctx->buf2 = lvgl_display_alloc_buffer(buf_size_bytes);
|
||||
if (ctx->buf2 == NULL) {
|
||||
lvgl_display_free_buffer(ctx->buf1);
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
@@ -432,7 +442,7 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
lvgl_display_free_buffer(ctx->buf1);
|
||||
lvgl_display_free_buffer(ctx->buf2);
|
||||
}
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
@@ -444,7 +454,7 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
lvgl_display_free_buffer(ctx->buf2);
|
||||
}
|
||||
lvgl_display_free_buffer(ctx->rotate_buf);
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
@@ -452,11 +462,11 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
||||
lv_display_set_color_format(disp, lv_color_format);
|
||||
lv_display_set_buffers(disp, ctx->buf1, ctx->buf2, buf_size_bytes, render_mode);
|
||||
lv_display_set_flush_cb(disp, lvgl_display_flush_cb);
|
||||
lv_display_set_driver_data(disp, ctx);
|
||||
lv_display_add_event_cb(disp, lvgl_display_rotation_event_cb, LV_EVENT_RESOLUTION_CHANGED, ctx);
|
||||
lv_display_set_driver_data(disp, wrapper);
|
||||
lv_display_add_event_cb(disp, lvgl_display_rotation_event_cb, LV_EVENT_RESOLUTION_CHANGED, wrapper);
|
||||
|
||||
// Apply once explicitly, independent of whether LV_EVENT_RESOLUTION_CHANGED fires on creation.
|
||||
lvgl_display_apply_rotation(ctx, lv_display_get_rotation(disp));
|
||||
lvgl_display_apply_rotation(wrapper, lv_display_get_rotation(disp));
|
||||
|
||||
*out_display = disp;
|
||||
return ERROR_NONE;
|
||||
@@ -467,10 +477,11 @@ void lvgl_display_remove(lv_display_t* display) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(display);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_display_get_driver_data(display);
|
||||
lv_display_delete(display);
|
||||
|
||||
if (ctx != NULL) {
|
||||
if (wrapper != NULL) {
|
||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)wrapper->context;
|
||||
if (ctx->owns_buffers) {
|
||||
if (ctx->buf1 != NULL) {
|
||||
lvgl_display_free_buffer(ctx->buf1);
|
||||
@@ -485,6 +496,6 @@ void lvgl_display_remove(lv_display_t* display) {
|
||||
if (ctx->ppa_handle != NULL) {
|
||||
lvgl_ppa_delete(ctx->ppa_handle);
|
||||
}
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl/devices/keyboard.h>
|
||||
#include <lvgl/devices/device_context.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include <tactility/drivers/keyboard.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <vector>
|
||||
|
||||
struct LvglKeyboardCtx {
|
||||
Device* device;
|
||||
};
|
||||
|
||||
static LvglSoftwareKeyboard last_software_keyboard = {
|
||||
.object = nullptr
|
||||
};
|
||||
@@ -39,10 +35,10 @@ void lvgl_keyboard_on_stop_lvgl() {
|
||||
}
|
||||
|
||||
static void lvgl_keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
LvglKeyboardCtx* ctx = static_cast<struct LvglKeyboardCtx*>(lv_indev_get_driver_data(indev));
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
|
||||
KeyboardKeyData key_data = {};
|
||||
if (keyboard_read_key(ctx->device, &key_data) != ERROR_NONE) {
|
||||
if (keyboard_read_key(wrapper->device, &key_data) != ERROR_NONE) {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
data->continue_reading = false;
|
||||
return;
|
||||
@@ -62,21 +58,21 @@ error_t lvgl_keyboard_add(struct Device* device, lv_display_t* display, lv_indev
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
LvglKeyboardCtx* ctx = static_cast<struct LvglKeyboardCtx*>(malloc(sizeof(struct LvglKeyboardCtx)));
|
||||
if (ctx == NULL) {
|
||||
auto* wrapper = new(std::nothrow) LvglDeviceContext(nullptr);
|
||||
if (wrapper == NULL) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->device = device;
|
||||
wrapper->device = device;
|
||||
|
||||
lv_indev_t* indev = lv_indev_create();
|
||||
if (indev == NULL) {
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD);
|
||||
lv_indev_set_read_cb(indev, lvgl_keyboard_read_cb);
|
||||
lv_indev_set_driver_data(indev, ctx);
|
||||
lv_indev_set_driver_data(indev, wrapper);
|
||||
if (display != NULL) {
|
||||
lv_indev_set_display(indev, display);
|
||||
}
|
||||
@@ -90,9 +86,9 @@ void lvgl_keyboard_remove(lv_indev_t* indev) {
|
||||
return;
|
||||
}
|
||||
|
||||
LvglKeyboardCtx* ctx = (struct LvglKeyboardCtx*)lv_indev_get_driver_data(indev);
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
lv_indev_delete(indev);
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
}
|
||||
|
||||
void lvgl_keyboard_enable(lv_indev_t* indev) {
|
||||
@@ -115,22 +111,21 @@ bool lvgl_hardware_keyboard_is_available() {
|
||||
}
|
||||
|
||||
void lvgl_hardware_keyboard_add_custom(lv_indev_t* indev) {
|
||||
LvglKeyboardCtx* ctx = static_cast<struct LvglKeyboardCtx*>(malloc(sizeof(struct LvglKeyboardCtx)));
|
||||
if (ctx == nullptr) {
|
||||
auto* wrapper = new(std::nothrow) LvglDeviceContext(nullptr);
|
||||
if (wrapper == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->device = nullptr;
|
||||
lv_indev_set_driver_data(indev, ctx);
|
||||
lv_indev_set_driver_data(indev, wrapper);
|
||||
|
||||
lvgl_keyboard_enable(indev);
|
||||
}
|
||||
|
||||
void lvgl_hardware_keyboard_remove_custom(lv_indev_t* indev) {
|
||||
lvgl_keyboard_disable(indev);
|
||||
auto* data = lv_indev_get_driver_data(indev);
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
lv_indev_set_driver_data(indev, nullptr);
|
||||
free(data); // LvglKeyboardCtx*
|
||||
delete wrapper;
|
||||
}
|
||||
|
||||
static void textarea_show_keyboard(lv_event_t* event) {
|
||||
|
||||
+23
-17
@@ -1,14 +1,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl/devices/pointer.h>
|
||||
#include <lvgl/devices/device_context.h>
|
||||
|
||||
#include <tactility/drivers/pointer.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define TAG "lvgl_pointer"
|
||||
constexpr auto* TAG = "lvgl_pointer";
|
||||
|
||||
struct LvglPointerCtx {
|
||||
struct Device* device;
|
||||
bool calibration_enabled;
|
||||
struct LvglPointerCalibration calibration;
|
||||
};
|
||||
@@ -60,13 +58,13 @@ static void lvgl_pointer_calibration_apply(
|
||||
// fixed pixel dimensions, not a rotation. This function has no notion of LVGL rotation at all:
|
||||
// calibration corrects the raw sensor's fixed physical mapping, which never changes with on-screen
|
||||
// orientation, so it doesn't belong anywhere near rotation math.
|
||||
static bool lvgl_pointer_read_calibrated(struct LvglPointerCtx* ctx, int32_t native_x_max, int32_t native_y_max, uint16_t* x, uint16_t* y) {
|
||||
if (pointer_read_data(ctx->device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) {
|
||||
static bool lvgl_pointer_read_calibrated(struct Device* device, struct LvglPointerCtx* ctx, int32_t native_x_max, int32_t native_y_max, uint16_t* x, uint16_t* y) {
|
||||
if (pointer_read_data(device, LVGL_POINTER_READ_TIMEOUT) != ERROR_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t point_count = 0;
|
||||
if (!pointer_get_touched_points(ctx->device, x, y, NULL, &point_count, 1) || point_count == 0) {
|
||||
if (!pointer_get_touched_points(device, x, y, NULL, &point_count, 1) || point_count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -82,7 +80,8 @@ static bool lvgl_pointer_read_calibrated(struct LvglPointerCtx* ctx, int32_t nat
|
||||
// logical space - unconditionally, since native-space coordinates always need this regardless of
|
||||
// whether calibration is enabled.
|
||||
static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)lv_indev_get_driver_data(indev);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)wrapper->context;
|
||||
lv_display_t* display = lv_indev_get_display(indev);
|
||||
|
||||
// lv_display_get_original_*_resolution() is the native (LV_DISPLAY_ROTATION_0) size,
|
||||
@@ -92,7 +91,7 @@ static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
|
||||
uint16_t x = 0;
|
||||
uint16_t y = 0;
|
||||
if (!lvgl_pointer_read_calibrated(ctx, native_x_max, native_y_max, &x, &y)) {
|
||||
if (!lvgl_pointer_read_calibrated(wrapper->device, ctx, native_x_max, native_y_max, &x, &y)) {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
return;
|
||||
}
|
||||
@@ -110,21 +109,26 @@ error_t lvgl_pointer_add(struct Device* device, lv_display_t* display, lv_indev_
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
struct LvglPointerCtx* ctx = calloc(1, sizeof(struct LvglPointerCtx));
|
||||
struct LvglPointerCtx* ctx = new(std::nothrow) LvglPointerCtx();
|
||||
if (ctx == NULL) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->device = device;
|
||||
struct LvglDeviceContext* wrapper = new(std::nothrow) LvglDeviceContext(ctx);
|
||||
if (wrapper == NULL) {
|
||||
delete ctx;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
wrapper->device = device;
|
||||
|
||||
lv_indev_t* indev = lv_indev_create();
|
||||
if (indev == NULL) {
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
|
||||
lv_indev_set_read_cb(indev, lvgl_pointer_read_cb);
|
||||
lv_indev_set_driver_data(indev, ctx);
|
||||
lv_indev_set_driver_data(indev, wrapper);
|
||||
if (display != NULL) {
|
||||
lv_indev_set_display(indev, display);
|
||||
}
|
||||
@@ -145,7 +149,8 @@ error_t lvgl_pointer_set_calibration(lv_indev_t* indev, const struct LvglPointer
|
||||
if (indev == NULL) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)wrapper->context;
|
||||
|
||||
if (calibration == NULL) {
|
||||
ctx->calibration_enabled = false;
|
||||
@@ -164,7 +169,8 @@ bool lvgl_pointer_get_calibration(lv_indev_t* indev, struct LvglPointerCalibrati
|
||||
if (indev == NULL || out_calibration == NULL) {
|
||||
return false;
|
||||
}
|
||||
struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
||||
struct LvglPointerCtx* ctx = (struct LvglPointerCtx*)wrapper->context;
|
||||
if (!ctx->calibration_enabled) {
|
||||
return false;
|
||||
}
|
||||
@@ -177,10 +183,10 @@ void lvgl_pointer_remove(lv_indev_t* indev) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct LvglPointerCtx* ctx = lv_indev_get_driver_data(indev);
|
||||
struct LvglDeviceContext* wrapper = (struct LvglDeviceContext*)lv_indev_get_driver_data(indev);
|
||||
if (default_pointer_indev == indev) {
|
||||
default_pointer_indev = NULL;
|
||||
}
|
||||
lv_indev_delete(indev);
|
||||
free(ctx);
|
||||
delete wrapper;
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lvgl/devices/trackball.h>
|
||||
#include <lvgl/devices/device_context.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
|
||||
#include <tactility/drivers/trackball.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
constexpr auto* TAG = "lvgl_trackball";
|
||||
|
||||
struct LvglTrackballCtx {
|
||||
LvglTrackballSettings settings;
|
||||
int32_t cursor_x;
|
||||
int32_t cursor_y;
|
||||
lv_obj_t* cursor;
|
||||
const void* cursor_image_src;
|
||||
};
|
||||
|
||||
static inline int32_t clamp(int32_t value, int32_t min_value, int32_t max_value) {
|
||||
if (value < min_value) return min_value;
|
||||
if (value > max_value) return max_value;
|
||||
return value;
|
||||
}
|
||||
|
||||
static void recenter_cursor(LvglTrackballCtx* ctx, lv_indev_t* indev) {
|
||||
lv_display_t* display = lv_indev_get_display(indev);
|
||||
// lv_display_get_original_*_resolution() is the native (LV_DISPLAY_ROTATION_0) size,
|
||||
// unaffected by the display's current rotation, matching lvgl/devices/pointer.c's approach.
|
||||
ctx->cursor_x = display != nullptr ? lv_display_get_original_horizontal_resolution(display) / 2 : 0;
|
||||
ctx->cursor_y = display != nullptr ? lv_display_get_original_vertical_resolution(display) / 2 : 0;
|
||||
}
|
||||
|
||||
// Creates the cursor object on first use and binds it to indev via lv_indev_set_cursor() exactly
|
||||
// once - LVGL's lv_indev_set_cursor() unconditionally reparents whatever it's given (even NULL:
|
||||
// it does not special-case that, see hide_cursor() below), so it must only ever be called here,
|
||||
// with a freshly-created, non-null object whose parent already matches (lv_layer_sys()), making
|
||||
// lv_obj_set_parent()'s "parent == obj->parent" check a safe no-op. Once bound, later image/
|
||||
// enabled changes update the existing object in place instead of re-binding.
|
||||
static void show_cursor(LvglTrackballCtx* ctx, lv_indev_t* indev) {
|
||||
if (ctx->cursor_image_src == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->cursor == nullptr) {
|
||||
ctx->cursor = lv_image_create(lv_layer_sys());
|
||||
if (ctx->cursor == nullptr) {
|
||||
return;
|
||||
}
|
||||
lv_obj_remove_flag(ctx->cursor, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_indev_set_cursor(indev, ctx->cursor);
|
||||
}
|
||||
|
||||
lv_image_set_src(ctx->cursor, ctx->cursor_image_src);
|
||||
if (ctx->settings.enabled) {
|
||||
lv_obj_remove_flag(ctx->cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(ctx->cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
// Only hides the cursor object - never deletes it
|
||||
static void hide_cursor(LvglTrackballCtx* ctx) {
|
||||
if (ctx->cursor == nullptr) {
|
||||
return;
|
||||
}
|
||||
lv_obj_add_flag(ctx->cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
static void lvgl_trackball_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
auto* ctx = static_cast<LvglTrackballCtx*>(wrapper->context);
|
||||
|
||||
// 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;
|
||||
trackball_read_delta(wrapper->device, &dx, &dy);
|
||||
if (!ctx->settings.enabled) {
|
||||
dx = 0;
|
||||
dy = 0;
|
||||
}
|
||||
|
||||
lv_display_t* display = lv_indev_get_display(indev);
|
||||
|
||||
if (ctx->settings.mode == LVGL_TRACKBALL_MODE_ENCODER) {
|
||||
int32_t ticks = (dx + dy) * static_cast<int32_t>(ctx->settings.encoder_sensitivity);
|
||||
data->enc_diff = static_cast<int16_t>(clamp(ticks, INT16_MIN, INT16_MAX));
|
||||
if (ticks != 0) {
|
||||
lv_display_trigger_activity(display);
|
||||
}
|
||||
} else {
|
||||
int32_t max_x = display != nullptr ? lv_display_get_original_horizontal_resolution(display) - 1 : 0;
|
||||
int32_t max_y = display != nullptr ? lv_display_get_original_vertical_resolution(display) - 1 : 0;
|
||||
ctx->cursor_x = clamp(ctx->cursor_x + dx * static_cast<int32_t>(ctx->settings.pointer_sensitivity), 0, max_x);
|
||||
ctx->cursor_y = clamp(ctx->cursor_y + dy * static_cast<int32_t>(ctx->settings.pointer_sensitivity), 0, max_y);
|
||||
data->point.x = static_cast<int16_t>(ctx->cursor_x);
|
||||
data->point.y = static_cast<int16_t>(ctx->cursor_y);
|
||||
}
|
||||
|
||||
bool pressed = false;
|
||||
if (ctx->settings.enabled) {
|
||||
trackball_get_button_pressed(wrapper->device, &pressed);
|
||||
}
|
||||
data->state = pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
|
||||
if (pressed) {
|
||||
lv_display_trigger_activity(display);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
LvglTrackballSettings lvgl_trackball_settings_get_default() {
|
||||
return LvglTrackballSettings {
|
||||
.mode = LVGL_TRACKBALL_MODE_ENCODER,
|
||||
.enabled = true,
|
||||
.encoder_sensitivity = 1,
|
||||
.pointer_sensitivity = 10,
|
||||
};
|
||||
}
|
||||
|
||||
error_t lvgl_trackball_add(struct Device* device, lv_display_t* display, lv_indev_t** out_indev) {
|
||||
if (device == nullptr || out_indev == nullptr) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (device_get_type(device) != &TRACKBALL_TYPE) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
auto* ctx = new(std::nothrow) LvglTrackballCtx();
|
||||
if (ctx == nullptr) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
ctx->settings = lvgl_trackball_settings_get_default();
|
||||
|
||||
auto* wrapper = new(std::nothrow) LvglDeviceContext(ctx);
|
||||
if (wrapper == nullptr) {
|
||||
delete ctx;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
wrapper->device = device;
|
||||
|
||||
lv_indev_t* indev = lv_indev_create();
|
||||
if (indev == nullptr) {
|
||||
delete wrapper;
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
|
||||
lv_indev_set_read_cb(indev, lvgl_trackball_read_cb);
|
||||
lv_indev_set_driver_data(indev, wrapper);
|
||||
if (display != nullptr) {
|
||||
lv_indev_set_display(indev, display);
|
||||
}
|
||||
recenter_cursor(ctx, indev);
|
||||
|
||||
*out_indev = indev;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
void lvgl_trackball_remove(lv_indev_t* indev) {
|
||||
if (indev == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
check(wrapper);
|
||||
auto* ctx = static_cast<LvglTrackballCtx*>(wrapper->context);
|
||||
check(ctx);
|
||||
if (ctx->cursor != nullptr) {
|
||||
lv_obj_delete(ctx->cursor);
|
||||
}
|
||||
lv_indev_delete(indev);
|
||||
delete wrapper;
|
||||
}
|
||||
|
||||
error_t lvgl_trackball_set_settings(lv_indev_t* indev, const struct LvglTrackballSettings* settings) {
|
||||
if (indev == nullptr || settings == nullptr) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (
|
||||
(settings->mode != LVGL_TRACKBALL_MODE_ENCODER && settings->mode != LVGL_TRACKBALL_MODE_POINTER) ||
|
||||
settings->encoder_sensitivity == 0 ||
|
||||
settings->pointer_sensitivity == 0
|
||||
) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
check(wrapper);
|
||||
auto* ctx = static_cast<LvglTrackballCtx*>(wrapper->context);
|
||||
check(ctx);
|
||||
bool mode_changed = ctx->settings.mode != settings->mode;
|
||||
ctx->settings = *settings;
|
||||
|
||||
if (mode_changed) {
|
||||
if (settings->mode == LVGL_TRACKBALL_MODE_POINTER) {
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
|
||||
recenter_cursor(ctx, indev);
|
||||
show_cursor(ctx, indev);
|
||||
} else {
|
||||
hide_cursor(ctx);
|
||||
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor visibility only tracks the enabled toggle in pointer mode - in encoder mode it must
|
||||
// stay hidden regardless of enabled, otherwise this unconditionally un-hides the cursor
|
||||
// hide_cursor() just hid above (enabled is independent of mode, and defaults to true).
|
||||
if (ctx->cursor != nullptr && ctx->settings.mode == LVGL_TRACKBALL_MODE_POINTER) {
|
||||
if (ctx->settings.enabled) {
|
||||
lv_obj_remove_flag(ctx->cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(ctx->cursor, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
bool lvgl_trackball_get_settings(lv_indev_t* indev, struct LvglTrackballSettings* out_settings) {
|
||||
if (indev == nullptr || out_settings == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
check(wrapper);
|
||||
auto* ctx = static_cast<LvglTrackballCtx*>(wrapper->context);
|
||||
check(ctx);
|
||||
*out_settings = ctx->settings;
|
||||
return true;
|
||||
}
|
||||
|
||||
void lvgl_trackball_set_cursor_image(lv_indev_t* indev, const void* image_src) {
|
||||
if (indev == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* wrapper = static_cast<LvglDeviceContext*>(lv_indev_get_driver_data(indev));
|
||||
check(wrapper);
|
||||
auto* ctx = static_cast<LvglTrackballCtx*>(wrapper->context);
|
||||
check(ctx);
|
||||
ctx->cursor_image_src = image_src;
|
||||
|
||||
if (ctx->settings.mode == LVGL_TRACKBALL_MODE_POINTER) {
|
||||
if (image_src == nullptr) {
|
||||
hide_cursor(ctx);
|
||||
} else {
|
||||
show_cursor(ctx, indev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
Reference in New Issue
Block a user