LVGL 库提交

This commit is contained in:
2026-08-30 08:57:07 +08:00
parent 26c9098630
commit e2f199c6bf
1056 changed files with 556743 additions and 45 deletions
File diff suppressed because it is too large Load Diff
+452
View File
@@ -0,0 +1,452 @@
/**
* @file lv_indev.h
*
*/
#ifndef LV_INDEV_H
#define LV_INDEV_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../core/lv_group.h"
#include "../misc/lv_area.h"
#include "../misc/lv_timer.h"
#include "../misc/lv_event.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/** Possible input device types*/
typedef enum {
LV_INDEV_TYPE_NONE, /**< Uninitialized state*/
LV_INDEV_TYPE_POINTER, /**< Touch pad, mouse, external button*/
LV_INDEV_TYPE_KEYPAD, /**< Keypad or keyboard*/
LV_INDEV_TYPE_BUTTON, /**< External (hardware button) which is assigned to a specific point of the screen*/
LV_INDEV_TYPE_ENCODER, /**< Encoder with only Left, Right turn and a Button*/
} lv_indev_type_t;
/** States for input devices*/
typedef enum {
LV_INDEV_STATE_RELEASED = 0,
LV_INDEV_STATE_PRESSED
} lv_indev_state_t;
typedef enum {
LV_INDEV_MODE_NONE = 0,
LV_INDEV_MODE_TIMER,
LV_INDEV_MODE_EVENT,
} lv_indev_mode_t;
/* Supported types of gestures */
typedef enum {
LV_INDEV_GESTURE_NONE = 0,
LV_INDEV_GESTURE_PINCH,
LV_INDEV_GESTURE_SWIPE,
LV_INDEV_GESTURE_ROTATE,
LV_INDEV_GESTURE_TWO_FINGERS_SWIPE,
LV_INDEV_GESTURE_SCROLL, /* Used with scrollwheels */
LV_INDEV_GESTURE_CNT, /* Total number of gestures types */
} lv_indev_gesture_type_t;
/** Data structure passed to an input driver to fill*/
typedef struct {
lv_point_t point; /**< For LV_INDEV_TYPE_POINTER the currently pressed point*/
uint32_t key; /**< For LV_INDEV_TYPE_KEYPAD the currently pressed key*/
uint32_t btn_id; /**< For LV_INDEV_TYPE_BUTTON the currently pressed button*/
int16_t enc_diff; /**< For LV_INDEV_TYPE_ENCODER number of steps since the previous read*/
lv_indev_state_t state; /**< LV_INDEV_STATE_RELEASED or LV_INDEV_STATE_PRESSED*/
bool continue_reading; /**< If set to true, the read callback is invoked again, unless the device is in event-driven mode*/
lv_indev_gesture_type_t gesture_type[LV_INDEV_GESTURE_CNT]; /* Current gesture types, per gesture */
void * gesture_data[LV_INDEV_GESTURE_CNT]; /* Used to store data per gesture */
} lv_indev_data_t;
typedef void (*lv_indev_read_cb_t)(lv_indev_t * indev, lv_indev_data_t * data);
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Create an indev
* @return Pointer to the created indev or NULL when allocation failed
*/
lv_indev_t * lv_indev_create(void);
/**
* Remove the provided input device. Make sure not to use the provided input device afterwards anymore.
* @param indev pointer to delete
*/
void lv_indev_delete(lv_indev_t * indev);
/**
* Get the next input device.
* @param indev pointer to the current input device. NULL to initialize.
* @return the next input device or NULL if there are no more. Provide the first input device when
* the parameter is NULL
*/
lv_indev_t * lv_indev_get_next(lv_indev_t * indev);
/**
* Read data from an input device.
* @param indev pointer to an input device
*/
void lv_indev_read(lv_indev_t * indev);
/**
* Called periodically to read the input devices
* @param timer pointer to a timer to read
*/
void lv_indev_read_timer_cb(lv_timer_t * timer);
/**
* Enable or disable one or all input devices (default enabled)
* @param indev pointer to an input device or NULL to enable/disable all of them
* @param enable true to enable, false to disable
*/
void lv_indev_enable(lv_indev_t * indev, bool enable);
/**
* Get the currently processed input device. Can be used in action functions too.
* @return pointer to the currently processed input device or NULL if no input device processing
* right now
*/
lv_indev_t * lv_indev_active(void);
/**
* Set the type of an input device
* @param indev pointer to an input device
* @param indev_type the type of the input device from `lv_indev_type_t` (`LV_INDEV_TYPE_...`)
*/
void lv_indev_set_type(lv_indev_t * indev, lv_indev_type_t indev_type);
/**
* Set a callback function to read input device data to the indev
* @param indev pointer to an input device
* @param read_cb pointer to callback function to read input device data
*/
void lv_indev_set_read_cb(lv_indev_t * indev, lv_indev_read_cb_t read_cb);
/**
* Set user data to the indev
* @param indev pointer to an input device
* @param user_data pointer to user data
*/
void lv_indev_set_user_data(lv_indev_t * indev, void * user_data);
/**
* Set driver data to the indev
* @param indev pointer to an input device
* @param driver_data pointer to driver data
*/
void lv_indev_set_driver_data(lv_indev_t * indev, void * driver_data);
/**
* Assign a display to the indev
* @param indev pointer to an input device
* @param disp pointer to an display
*/
void lv_indev_set_display(lv_indev_t * indev, struct _lv_display_t * disp);
/**
* Set long press time to indev
* @param indev pointer to input device
* @param long_press_time time long press time in ms
*/
void lv_indev_set_long_press_time(lv_indev_t * indev, uint16_t long_press_time);
/**
* Set long press repeat time to indev
* @param indev pointer to input device
* @param long_press_repeat_time long press repeat time in ms
*/
void lv_indev_set_long_press_repeat_time(lv_indev_t * indev, uint16_t long_press_repeat_time);
/**
* Set scroll limit to the input device
* @param indev pointer to an input device
* @param scroll_limit the number of pixels to slide before actually drag the object
*/
void lv_indev_set_scroll_limit(lv_indev_t * indev, uint8_t scroll_limit);
/**
* Set scroll throw slow-down to the indev. Greater value means faster slow-down
* @param indev pointer to an input device
* @param scroll_throw the slow-down in [%]
*/
void lv_indev_set_scroll_throw(lv_indev_t * indev, uint8_t scroll_throw);
/**
* Get the type of an input device
* @param indev pointer to an input device
* @return the type of the input device from `lv_hal_indev_type_t` (`LV_INDEV_TYPE_...`)
*/
lv_indev_type_t lv_indev_get_type(const lv_indev_t * indev);
/**
* Get the callback function to read input device data to the indev
* @param indev pointer to an input device
* @return Pointer to callback function to read input device data or NULL if indev is NULL
*/
lv_indev_read_cb_t lv_indev_get_read_cb(lv_indev_t * indev);
/**
* Get the indev state
* @param indev pointer to an input device
* @return Indev state or LV_INDEV_STATE_RELEASED if indev is NULL
*/
lv_indev_state_t lv_indev_get_state(const lv_indev_t * indev);
/**
* Get the indev assigned group
* @param indev pointer to an input device
* @return Pointer to indev assigned group or NULL if indev is NULL
*/
lv_group_t * lv_indev_get_group(const lv_indev_t * indev);
/**
* Get a pointer to the assigned display of the indev
* @param indev pointer to an input device
* @return pointer to the assigned display or NULL if indev is NULL
*/
lv_display_t * lv_indev_get_display(const lv_indev_t * indev);
/**
* Get a pointer to the user data of the indev
* @param indev pointer to an input device
* @return pointer to the user data or NULL if indev is NULL
*/
void * lv_indev_get_user_data(const lv_indev_t * indev);
/**
* Get a pointer to the driver data of the indev
* @param indev pointer to an input device
* @return pointer to the driver data or NULL if indev is NULL
*/
void * lv_indev_get_driver_data(const lv_indev_t * indev);
/**
* Get whether indev is moved while pressed
* @param indev pointer to an input device
* @return true: indev is moved while pressed; false: indev is not moved while pressed
*/
bool lv_indev_get_press_moved(const lv_indev_t * indev);
/**
* Reset one or all input devices
* @param indev pointer to an input device to reset or NULL to reset all of them
* @param obj pointer to an object which triggers the reset.
*/
void lv_indev_reset(lv_indev_t * indev, lv_obj_t * obj);
/**
* Touch and key related events are sent to the input device first and to the widget after that.
* If this functions called in an indev event, the event won't be sent to the widget.
* @param indev pointer to an input device
*/
void lv_indev_stop_processing(lv_indev_t * indev);
/**
* Reset the long press state of an input device
* @param indev pointer to an input device
*/
void lv_indev_reset_long_press(lv_indev_t * indev);
/**
* Set a cursor for a pointer input device (for LV_INPUT_TYPE_POINTER and LV_INPUT_TYPE_BUTTON)
* @param indev pointer to an input device
* @param cur_obj pointer to an object to be used as cursor
*/
void lv_indev_set_cursor(lv_indev_t * indev, lv_obj_t * cur_obj);
/**
* Set a destination group for a keypad input device (for LV_INDEV_TYPE_KEYPAD)
* @param indev pointer to an input device
* @param group pointer to a group
*/
void lv_indev_set_group(lv_indev_t * indev, lv_group_t * group);
/**
* Set the an array of points for LV_INDEV_TYPE_BUTTON.
* These points will be assigned to the buttons to press a specific point on the screen
* @param indev pointer to an input device
* @param points array of points
*/
void lv_indev_set_button_points(lv_indev_t * indev, const lv_point_t points[]);
/**
* Get the last point of an input device (for LV_INDEV_TYPE_POINTER and LV_INDEV_TYPE_BUTTON)
* @param indev pointer to an input device
* @param point pointer to a point to store the result
*/
void lv_indev_get_point(const lv_indev_t * indev, lv_point_t * point);
/**
* Get the current gesture direct
* @param indev pointer to an input device
* @return current gesture direct
*/
lv_dir_t lv_indev_get_gesture_dir(const lv_indev_t * indev);
/**
* Get the last pressed key of an input device (for LV_INDEV_TYPE_KEYPAD)
* @param indev pointer to an input device
* @return the last pressed key (0 on error)
*/
uint32_t lv_indev_get_key(const lv_indev_t * indev);
/**
* Get the counter for consecutive clicks within a short distance and time.
* The counter is updated before LV_EVENT_SHORT_CLICKED is fired.
* @param indev pointer to an input device
* @return short click streak counter
*/
uint8_t lv_indev_get_short_click_streak(const lv_indev_t * indev);
/**
* Check the current scroll direction of an input device (for LV_INDEV_TYPE_POINTER and
* LV_INDEV_TYPE_BUTTON)
* @param indev pointer to an input device
* @return LV_DIR_NONE: no scrolling now
* LV_DIR_HOR/VER
*/
lv_dir_t lv_indev_get_scroll_dir(const lv_indev_t * indev);
/**
* Get the currently scrolled object (for LV_INDEV_TYPE_POINTER and
* LV_INDEV_TYPE_BUTTON)
* @param indev pointer to an input device
* @return pointer to the currently scrolled object or NULL if no scrolling by this indev
*/
lv_obj_t * lv_indev_get_scroll_obj(const lv_indev_t * indev);
/**
* Get the movement vector of an input device (for LV_INDEV_TYPE_POINTER and
* LV_INDEV_TYPE_BUTTON)
* @param indev pointer to an input device
* @param point pointer to a point to store the types.pointer.vector
*/
void lv_indev_get_vect(const lv_indev_t * indev, lv_point_t * point);
/**
* Get the cursor object of an input device (for LV_INDEV_TYPE_POINTER only)
* @param indev pointer to an input device
* @return pointer to the cursor object
*/
lv_obj_t * lv_indev_get_cursor(lv_indev_t * indev);
/**
* Do nothing until the next release
* @param indev pointer to an input device
*/
void lv_indev_wait_release(lv_indev_t * indev);
/**
* Gets a pointer to the currently active object in the currently processed input device.
* @return pointer to currently active object or NULL if no active object
*/
lv_obj_t * lv_indev_get_active_obj(void);
/**
* Get a pointer to the indev read timer to
* modify its parameters with `lv_timer_...` functions.
* @param indev pointer to an input device
* @return pointer to the indev read refresher timer. (NULL on error)
*/
lv_timer_t * lv_indev_get_read_timer(lv_indev_t * indev);
/**
* Set the input device's event model: event-driven mode or timer mode.
* @param indev pointer to an input device
* @param mode the mode of input device
*/
void lv_indev_set_mode(lv_indev_t * indev, lv_indev_mode_t mode);
/**
* Get the input device's running mode.
* @param indev pointer to an input device
* @return the running mode for the specified input device.
*/
lv_indev_mode_t lv_indev_get_mode(lv_indev_t * indev);
/**
* Search the most top, clickable object by a point
* @param obj pointer to a start object, typically the screen
* @param point pointer to a point for searching the most top child
* @return pointer to the found object or NULL if there was no suitable object
*/
lv_obj_t * lv_indev_search_obj(lv_obj_t * obj, lv_point_t * point);
/**
* Add an event handler to the indev
* @param indev pointer to an indev
* @param event_cb an event callback
* @param filter event code to react or `LV_EVENT_ALL`
* @param user_data optional user_data
*/
void lv_indev_add_event_cb(lv_indev_t * indev, lv_event_cb_t event_cb, lv_event_code_t filter, void * user_data);
/**
* Get the number of event attached to an indev
* @param indev pointer to an indev
* @return number of events
*/
uint32_t lv_indev_get_event_count(lv_indev_t * indev);
/**
* Get an event descriptor for an event
* @param indev pointer to an indev
* @param index the index of the event
* @return the event descriptor
*/
lv_event_dsc_t * lv_indev_get_event_dsc(lv_indev_t * indev, uint32_t index);
/**
* Remove an event
* @param indev pointer to an indev
* @param index the index of the event to remove
* @return true: and event was removed; false: no event was removed
*/
bool lv_indev_remove_event(lv_indev_t * indev, uint32_t index);
/**
* Remove an event_cb with user_data
* @param indev pointer to a indev
* @param event_cb the event_cb of the event to remove
* @param user_data user_data
* @return the count of the event removed
*/
uint32_t lv_indev_remove_event_cb_with_user_data(lv_indev_t * indev, lv_event_cb_t event_cb, void * user_data);
/**
* Send an event to an indev
* @param indev pointer to an indev
* @param code an event code. LV_EVENT_...
* @param param optional param
* @return LV_RESULT_OK: indev wasn't deleted in the event.
*/
lv_result_t lv_indev_send_event(lv_indev_t * indev, lv_event_code_t code, void * param);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_INDEV_H*/
+964
View File
@@ -0,0 +1,964 @@
/******************************************************************
* @file lv_indev_gesture.c
*
* Recognize gestures that consist of multiple touch events
*
* Copyright (c) 2024 EDGEMTech Ltd
*
* Author EDGEMTech Ltd. (erik.tagirov@edgemtech.ch)
*
******************************************************************/
/********************
* INCLUDES
********************/
#include "lv_indev_private.h"
#include "../misc/lv_event_private.h"
#if LV_USE_GESTURE_RECOGNITION
#include <math.h>
#include "lv_indev_gesture.h"
#include "lv_indev_gesture_private.h"
/********************
* DEFINES
********************/
#define LV_GESTURE_PINCH_DOWN_THRESHOLD 0.75f /* Default value - start sending events when reached */
#define LV_GESTURE_PINCH_UP_THRESHOLD 1.5f /* Default value - start sending events when reached */
#define LV_GESTURE_PINCH_MAX_INITIAL_SCALE 2.5f /* Default value */
#define LV_GESTURE_ROTATION_ANGLE_RAD_THRESHOLD 0.2f /* Default value - start sending events when reached */
/********************
* TYPEDEFS
********************/
/********************
* STATIC PROTOTYPES
********************/
static lv_indev_gesture_t * init_gesture_info(void);
static lv_indev_gesture_motion_t * get_motion(uint8_t id, lv_indev_gesture_t * info);
static int8_t get_motion_idx(uint8_t id, lv_indev_gesture_t * info);
static void process_touch_event(lv_indev_touch_data_t * touch, lv_indev_gesture_t * info);
static void gesture_update_center_point(lv_indev_gesture_t * gesture, int touch_points_nb);
static void gesture_calculate_factors(lv_indev_gesture_t * gesture, int touch_points_nb);
static void reset_recognizer(lv_indev_gesture_recognizer_t * recognizer);
static lv_indev_gesture_recognizer_t * lv_indev_get_gesture_recognizer(lv_event_t * gesture_event,
lv_indev_gesture_type_t type);
static lv_dir_t calculate_swipe_dir(lv_indev_gesture_recognizer_t * recognizer);
static lv_indev_gesture_type_t get_first_recognized_or_ended_gesture(lv_indev_t * indev);
/********************
* STATIC VARIABLES
********************/
/********************
* MACROS
********************/
#define SQUARE(x) ((x) * (x))
#define SQUARE_SUM(x, y) (SQUARE(x) + SQUARE(y))
/********************
* GLOBAL FUNCTIONS
********************/
void lv_indev_set_pinch_up_threshold(lv_indev_t * indev, float threshold)
{
/* A up threshold MUST always be bigger than 1 */
LV_ASSERT(threshold > 1.0f);
lv_indev_gesture_recognizer_t * recognizer = &indev->recognizers[LV_INDEV_GESTURE_PINCH];
if(recognizer->config == NULL) {
recognizer->config = lv_malloc_zeroed(sizeof(lv_indev_gesture_configuration_t));
LV_ASSERT_MALLOC(recognizer->config);
recognizer->config->pinch_down_threshold = LV_GESTURE_PINCH_DOWN_THRESHOLD;
}
recognizer->config->pinch_up_threshold = threshold;
}
void lv_indev_set_pinch_down_threshold(lv_indev_t * indev, float threshold)
{
/* A down threshold MUST always be smaller than 1 */
LV_ASSERT(threshold < 1.0f);
lv_indev_gesture_recognizer_t * recognizer = &indev->recognizers[LV_INDEV_GESTURE_PINCH];
if(recognizer->config == NULL) {
recognizer->config = lv_malloc_zeroed(sizeof(lv_indev_gesture_configuration_t));
LV_ASSERT_MALLOC(recognizer->config);
recognizer->config->pinch_up_threshold = LV_GESTURE_PINCH_UP_THRESHOLD;
}
recognizer->config->pinch_down_threshold = threshold;
}
void lv_indev_set_rotation_rad_threshold(lv_indev_t * indev, float threshold)
{
/* A rotation threshold MUST always be a positive number */
LV_ASSERT(threshold > 0.0f);
lv_indev_gesture_recognizer_t * recognizer = &indev->recognizers[LV_INDEV_GESTURE_ROTATE];
if(recognizer->config == NULL) {
recognizer->config = lv_malloc_zeroed(sizeof(lv_indev_gesture_configuration_t));
LV_ASSERT(recognizer->config != NULL);
recognizer->config->rotation_angle_rad_threshold = LV_GESTURE_ROTATION_ANGLE_RAD_THRESHOLD;
}
recognizer->config->rotation_angle_rad_threshold = threshold;
}
void lv_indev_get_gesture_primary_point(lv_indev_gesture_recognizer_t * recognizer, lv_point_t * point)
{
if(recognizer->info->motions[0].finger != -1) {
point->x = recognizer->info->motions[0].point.x;
point->y = recognizer->info->motions[0].point.y;
return;
}
/* There are currently no active contact points */
point->x = 0;
point->y = 0;
}
bool lv_indev_recognizer_is_active(lv_indev_gesture_recognizer_t * recognizer)
{
if(recognizer->state == LV_INDEV_GESTURE_STATE_ENDED ||
recognizer->info->finger_cnt == 0) {
return false;
}
return true;
}
float lv_event_get_pinch_scale(lv_event_t * gesture_event)
{
lv_indev_gesture_recognizer_t * recognizer;
if((recognizer = lv_indev_get_gesture_recognizer(gesture_event, LV_INDEV_GESTURE_PINCH)) == NULL) {
return 0.0f;
}
return recognizer->scale;
}
float lv_event_get_rotation(lv_event_t * gesture_event)
{
lv_indev_gesture_recognizer_t * recognizer;
if((recognizer = lv_indev_get_gesture_recognizer(gesture_event, LV_INDEV_GESTURE_ROTATE)) == NULL) {
return 0.0f;
}
return recognizer->rotation;
}
float lv_event_get_two_fingers_swipe_distance(lv_event_t * gesture_event)
{
lv_indev_gesture_recognizer_t * recognizer;
if((recognizer = lv_indev_get_gesture_recognizer(gesture_event, LV_INDEV_GESTURE_TWO_FINGERS_SWIPE)) == NULL) {
return 0.0f;
}
return recognizer->distance;
}
lv_dir_t lv_event_get_two_fingers_swipe_dir(lv_event_t * gesture_event)
{
lv_indev_gesture_recognizer_t * recognizer;
if((recognizer = lv_indev_get_gesture_recognizer(gesture_event, LV_INDEV_GESTURE_TWO_FINGERS_SWIPE)) == NULL) {
return LV_DIR_NONE;
}
return recognizer->two_fingers_swipe_dir;
}
void lv_indev_get_gesture_center_point(lv_indev_gesture_recognizer_t * recognizer, lv_point_t * point)
{
if(lv_indev_recognizer_is_active(recognizer) == false) {
point->x = 0;
point->y = 0;
return;
}
point->x = recognizer->info->center.x;
point->y = recognizer->info->center.y;
}
lv_indev_gesture_state_t lv_event_get_gesture_state(lv_event_t * gesture_event, lv_indev_gesture_type_t type)
{
lv_indev_gesture_recognizer_t * recognizer;
if((recognizer = lv_indev_get_gesture_recognizer(gesture_event, type)) == NULL) {
return LV_INDEV_GESTURE_STATE_NONE;
}
return recognizer->state;
}
lv_indev_gesture_type_t lv_event_get_gesture_type(lv_event_t * gesture_event)
{
lv_indev_t * indev = (lv_indev_t *) gesture_event->param;
if(indev == NULL) {
return LV_INDEV_GESTURE_NONE;
}
return indev->cur_gesture;
}
void lv_indev_set_gesture_data(lv_indev_data_t * data, lv_indev_gesture_recognizer_t * recognizer,
lv_indev_gesture_type_t type)
{
bool is_active;
lv_point_t cur_pnt;
if(recognizer == NULL) return;
/* If there is a single contact point use its coords,
* when there are no contact points it's set to 0,0
*
* Note: If a gesture was detected, the primary point is overwritten below
*/
lv_indev_get_gesture_primary_point(recognizer, &cur_pnt);
data->point.x = cur_pnt.x;
data->point.y = cur_pnt.y;
data->gesture_type[type] = LV_INDEV_GESTURE_NONE;
data->gesture_data[type] = NULL;
/* The call below returns false if there are no active contact points */
/* - OR when the gesture has ended, false is considered as a RELEASED state */
is_active = lv_indev_recognizer_is_active(recognizer);
if(is_active == false) {
data->state = LV_INDEV_STATE_RELEASED;
}
else {
data->state = LV_INDEV_STATE_PRESSED;
}
switch(recognizer->state) {
case LV_INDEV_GESTURE_STATE_RECOGNIZED:
lv_indev_get_gesture_center_point(recognizer, &cur_pnt);
data->point.x = cur_pnt.x;
data->point.y = cur_pnt.y;
data->gesture_type[type] = type;
data->gesture_data[type] = (void *) recognizer;
break;
case LV_INDEV_GESTURE_STATE_ENDED:
data->gesture_type[type] = type;
data->gesture_data[type] = (void *) recognizer;
break;
default:
break;
}
}
void lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer, lv_indev_touch_data_t * touches,
uint16_t touch_cnt)
{
lv_indev_touch_data_t * touch;
lv_indev_gesture_recognizer_t * r = recognizer;
uint8_t i;
if(r->info == NULL) {
LV_LOG_TRACE("init gesture info");
r->info = init_gesture_info();
}
if(r->config == NULL) {
LV_LOG_TRACE("init gesture configuration - set defaults");
r->config = lv_malloc_zeroed(sizeof(lv_indev_gesture_configuration_t));
LV_ASSERT_MALLOC(r->config);
r->config->pinch_up_threshold = LV_GESTURE_PINCH_UP_THRESHOLD;
r->config->pinch_down_threshold = LV_GESTURE_PINCH_DOWN_THRESHOLD;
}
/* Process collected touch events */
for(i = 0; i < touch_cnt; i++) {
touch = touches;
process_touch_event(touch, r->info);
touches++;
LV_LOG_TRACE("processed touch ev: %d finger id: %d state: %d x: %" LV_PRId32 " y: %" LV_PRId32 " finger_cnt: %d",
i, touch->id, touch->state, touch->point.x, touch->point.y, r->info->finger_cnt);
}
LV_LOG_TRACE("Current finger count: %d state: %d", r->info->finger_cnt, r->state);
if(r->info->finger_cnt == 2) {
switch(r->state) {
case LV_INDEV_GESTURE_STATE_NONE:
/* 2 fingers down - potential pinch or swipe */
reset_recognizer(recognizer);
gesture_update_center_point(r->info, 2);
r->state = LV_INDEV_GESTURE_STATE_ONGOING;
break;
case LV_INDEV_GESTURE_STATE_ONGOING:
gesture_calculate_factors(r->info, 2);
if(r->info->scale > LV_GESTURE_PINCH_MAX_INITIAL_SCALE) {
r->state = LV_INDEV_GESTURE_STATE_CANCELED;
break;
}
if(r->info->scale > r->config->pinch_up_threshold ||
r->info->scale < r->config->pinch_down_threshold) {
r->state = LV_INDEV_GESTURE_STATE_RECOGNIZED;
}
break;
case LV_INDEV_GESTURE_STATE_RECOGNIZED:
/* It's an ongoing pinch gesture - update the factors */
gesture_calculate_factors(r->info, 2);
LV_ASSERT(r->info != NULL);
r->scale = r->info->scale;
r->type = LV_INDEV_GESTURE_PINCH;
break;
case LV_INDEV_GESTURE_STATE_ENDED:
case LV_INDEV_GESTURE_STATE_CANCELED:
reset_recognizer(r);
break;
default:
LV_ASSERT_MSG(true, "invalid gesture recognizer state");
}
}
else {
switch(r->state) {
case LV_INDEV_GESTURE_STATE_RECOGNIZED:
/* Gesture has ended */
r->state = LV_INDEV_GESTURE_STATE_ENDED;
break;
case LV_INDEV_GESTURE_STATE_ONGOING:
/* User lifted a finger before reaching threshold */
r->state = LV_INDEV_GESTURE_STATE_CANCELED;
break;
case LV_INDEV_GESTURE_STATE_ENDED:
case LV_INDEV_GESTURE_STATE_CANCELED:
reset_recognizer(r);
break;
default:
LV_ASSERT_MSG(true, "invalid gesture recognizer state");
}
}
}
void lv_indev_gesture_detect_rotation(lv_indev_gesture_recognizer_t * recognizer, lv_indev_touch_data_t * touches,
uint16_t touch_cnt)
{
lv_indev_touch_data_t * touch;
lv_indev_gesture_recognizer_t * r = recognizer;
uint8_t i;
if(r->info == NULL) {
LV_LOG_TRACE("init gesture info");
r->info = init_gesture_info();
}
if(r->config == NULL) {
LV_LOG_TRACE("init gesture configuration - set defaults");
r->config = lv_malloc_zeroed(sizeof(lv_indev_gesture_configuration_t));
LV_ASSERT(r->config != NULL);
}
/* Process collected touch events */
for(i = 0; i < touch_cnt; i++) {
touch = touches;
process_touch_event(touch, r->info);
touches++;
LV_LOG_TRACE("processed touch ev: %d finger id: %d state: %d x: %" LV_PRId32 " y: %" LV_PRId32 " finger_cnt: %d",
i, touch->id, touch->state, touch->point.x, touch->point.y, r->info->finger_cnt);
}
LV_LOG_TRACE("Current finger count: %d state: %d", r->info->finger_cnt, r->state);
if(r->info->finger_cnt == 2) {
switch(r->state) {
case LV_INDEV_GESTURE_STATE_NONE:
/* 2 fingers down - potential rotation or swipe */
reset_recognizer(recognizer);
gesture_update_center_point(r->info, 2);
r->state = LV_INDEV_GESTURE_STATE_ONGOING;
break;
case LV_INDEV_GESTURE_STATE_ONGOING:
/* Update the rotation from the inputs */
gesture_calculate_factors(r->info, 2);
if(fabs(r->info->rotation - r->info->p_rotation) > r->config->rotation_angle_rad_threshold) {
gesture_update_center_point(r->info, 2);
r->state = LV_INDEV_GESTURE_STATE_RECOGNIZED;
}
break;
case LV_INDEV_GESTURE_STATE_RECOGNIZED:
/* It's a recognized rotation gesture - update the factors */
gesture_calculate_factors(r->info, 2);
r->type = LV_INDEV_GESTURE_ROTATE;
r->rotation = r->info->rotation;
break;
case LV_INDEV_GESTURE_STATE_ENDED:
case LV_INDEV_GESTURE_STATE_CANCELED:
reset_recognizer(r);
r->type = LV_INDEV_GESTURE_NONE;
r->state = LV_INDEV_GESTURE_STATE_CANCELED;
break;
default:
LV_ASSERT_MSG(true, "invalid gesture recognizer state");
}
}
else {
switch(r->state) {
case LV_INDEV_GESTURE_STATE_RECOGNIZED:
/* Gesture has ended */
r->type = LV_INDEV_GESTURE_ROTATE;
r->state = LV_INDEV_GESTURE_STATE_ENDED;
break;
case LV_INDEV_GESTURE_STATE_ONGOING:
/* User lifted a finger before reaching threshold */
reset_recognizer(r);
break;
case LV_INDEV_GESTURE_STATE_CANCELED:
case LV_INDEV_GESTURE_STATE_ENDED:
reset_recognizer(r);
break;
default:
LV_ASSERT_MSG(true, "invalid gesture recognizer state");
}
}
}
void lv_indev_gesture_detect_two_fingers_swipe(lv_indev_gesture_recognizer_t * recognizer,
lv_indev_touch_data_t * touches,
uint16_t touch_cnt)
{
lv_indev_touch_data_t * touch;
lv_indev_gesture_recognizer_t * r = recognizer;
uint8_t i;
float dist;
if(r->info == NULL) {
LV_LOG_TRACE("init gesture info");
r->info = init_gesture_info();
}
if(r->config == NULL) {
LV_LOG_TRACE("init gesture configuration - set defaults");
r->config = lv_malloc_zeroed(sizeof(lv_indev_gesture_configuration_t));
LV_ASSERT(r->config != NULL);
}
/* Process collected touch events */
for(i = 0; i < touch_cnt; i++) {
touch = touches;
process_touch_event(touch, r->info);
touches++;
LV_LOG_TRACE("processed touch ev: %d finger id: %d state: %d x: %" LV_PRId32 " y: %" LV_PRId32 " finger_cnt: %d",
i, touch->id, touch->state, touch->point.x, touch->point.y, r->info->finger_cnt);
}
LV_LOG_TRACE("Current finger count: %d state: %d", r->info->finger_cnt, r->state);
if(r->info->finger_cnt == 2) {
switch(r->state) {
case LV_INDEV_GESTURE_STATE_NONE:
/* 2 fingers down - potential rotation or swipe */
reset_recognizer(recognizer);
gesture_update_center_point(r->info, 2);
r->state = LV_INDEV_GESTURE_STATE_ONGOING;
break;
case LV_INDEV_GESTURE_STATE_ONGOING:
/* The gesture is ongoing, now wait for the distance from the center
to be higher than the threshold to pass it as recognized */
gesture_calculate_factors(r->info, 2);
dist = SQUARE_SUM(r->info->delta_x, r->info->delta_y);
if(dist > SQUARE(lv_indev_active()->gesture_limit)) {
r->state = LV_INDEV_GESTURE_STATE_RECOGNIZED;
}
break;
case LV_INDEV_GESTURE_STATE_RECOGNIZED:
/* The gesture is now recognized, and will stay recognized
until a finger is lifted */
gesture_calculate_factors(r->info, 2);
r->distance = (float) sqrt(SQUARE_SUM(r->info->delta_x, r->info->delta_y));
r->two_fingers_swipe_dir = calculate_swipe_dir(r);
r->type = LV_INDEV_GESTURE_TWO_FINGERS_SWIPE;
break;
case LV_INDEV_GESTURE_STATE_ENDED:
case LV_INDEV_GESTURE_STATE_CANCELED:
reset_recognizer(r);
break;
default:
LV_ASSERT_MSG(true, "invalid gesture recognizer state");
}
}
else {
switch(r->state) {
case LV_INDEV_GESTURE_STATE_RECOGNIZED:
/* Gesture has ended */
r->state = LV_INDEV_GESTURE_STATE_ENDED;
r->type = LV_INDEV_GESTURE_TWO_FINGERS_SWIPE;
break;
case LV_INDEV_GESTURE_STATE_ONGOING:
/* User lifted a finger before reaching threshold */
reset_recognizer(r);
r->state = LV_INDEV_GESTURE_STATE_ENDED;
break;
case LV_INDEV_GESTURE_STATE_CANCELED:
case LV_INDEV_GESTURE_STATE_ENDED:
reset_recognizer(r);
r->state = LV_INDEV_GESTURE_STATE_NONE;
break;
default:
LV_ASSERT_MSG(true, "invalid gesture recognizer state");
}
}
}
void lv_indev_gesture_recognizers_update(lv_indev_t * indev, lv_indev_touch_data_t * touches, uint16_t touch_cnt)
{
lv_indev_gesture_type_t type;
/* First check if a recognizer state is RECOGNIZED or ENDED. *
* In that case, call its recongizer function and reset the other*/
type = get_first_recognized_or_ended_gesture(indev);
if(type != LV_INDEV_GESTURE_NONE) {
for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) {
if(indev->recognizers[i].recog_fn != NULL) {
/* Update all recognizers to let them process input */
indev->recognizers[i].recog_fn(&indev->recognizers[i], &touches[0], touch_cnt);
/* Then reset the recognizers which did not repport RECONIZED or ENDED */
if(((lv_indev_gesture_type_t)i) != type) {
reset_recognizer(&indev->recognizers[i]);
}
}
}
}
else {
/* Otherwise call all recognizer functions, and stop as soon as one recognizer *
* reports the state RECOGNIZED or ENDED */
for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) {
if(indev->recognizers[i].recog_fn != NULL) {
indev->recognizers[i].recog_fn(&indev->recognizers[i], &touches[0], touch_cnt);
/* If the new state is RECOGNIZED or ENDED */
if(indev->recognizers[i].state == LV_INDEV_GESTURE_STATE_RECOGNIZED ||
indev->recognizers[i].state == LV_INDEV_GESTURE_STATE_ENDED) {
/* Reset the others registered recognizers */
for(int j = 0; j < LV_INDEV_GESTURE_CNT; j++) {
if(j != i && indev->recognizers[j].recog_fn != NULL) {
reset_recognizer(&indev->recognizers[j]);
}
}
break;
}
}
}
}
}
void lv_indev_gesture_recognizers_set_data(lv_indev_t * indev, lv_indev_data_t * data)
{
lv_indev_gesture_type_t type;
type = get_first_recognized_or_ended_gesture(indev);
/* If a gesture is RECOGNIZED or ENDED, set only its data */
if(type != LV_INDEV_GESTURE_NONE) {
lv_indev_set_gesture_data(data, &indev->recognizers[type], type);
}
else {
/* Otherwise, set data from all initialized recognizer */
for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) {
if(indev->recognizers[i].recog_fn != NULL) {
lv_indev_set_gesture_data(data, &indev->recognizers[i], i);
}
}
}
}
/********************
* STATIC FUNCTIONS
********************/
/**
* Caluclate the direction from the starting center of a two fingers swipe gesture
* @param recognizer pointer to the recognizer handling the two fingers
* swipe gesture
* @return the direction of the swipe, from the starting center
*/
static lv_dir_t calculate_swipe_dir(lv_indev_gesture_recognizer_t * recognizer)
{
float abs_x = LV_ABS(recognizer->info->delta_x);
float abs_y = LV_ABS(recognizer->info->delta_y);
if(abs_x > abs_y) {
return recognizer->info->delta_x > 0 ? LV_DIR_RIGHT : LV_DIR_LEFT;
}
else {
return recognizer->info->delta_y > 0 ? LV_DIR_BOTTOM : LV_DIR_TOP;
}
}
/**
* Get the gesture recognizer associated to the event
* @param gesture_event an LV_GESTURE_EVENT event
* @param type the type of the recognizer we want to get
* @return a pointer to the gesture recognizer that emitted the event
*/
lv_indev_gesture_recognizer_t * lv_indev_get_gesture_recognizer(lv_event_t * gesture_event,
lv_indev_gesture_type_t type)
{
lv_indev_t * indev;
if(gesture_event == NULL || gesture_event->param == NULL) return NULL;
indev = (lv_indev_t *) gesture_event->param;
if(indev == NULL || indev->gesture_data[type] == NULL) return NULL;
return (lv_indev_gesture_recognizer_t *) indev->gesture_data[type];
}
/**
* Resets a gesture recognizer, motion descriptors are preserved
* @param recognizer a pointer to the recognizer to reset
*/
static void reset_recognizer(lv_indev_gesture_recognizer_t * recognizer)
{
uint8_t finger_cnt;
lv_indev_gesture_t * info;
lv_indev_gesture_configuration_t * conf;
lv_recognizer_func_t recog_fn;
if(recognizer == NULL) return;
finger_cnt = recognizer->info->finger_cnt;
info = recognizer->info;
conf = recognizer->config;
recog_fn = recognizer->recog_fn;
/* Set everything to zero but preserve the motion descriptors,
* which are located at the start of the lv_indev_gesture_t struct */
lv_memzero((uint8_t *)info + sizeof(info->motions), sizeof(lv_indev_gesture_t) - sizeof(info->motions));
lv_memzero(recognizer, sizeof(lv_indev_gesture_recognizer_t));
recognizer->info = info;
recognizer->config = conf;
recognizer->recog_fn = recog_fn;
recognizer->scale = recognizer->info->scale = 1.0;
recognizer->info->finger_cnt = finger_cnt;
recognizer->state = LV_INDEV_GESTURE_STATE_NONE;
recognizer->type = LV_INDEV_GESTURE_NONE;
}
/**
* Initializes a motion descriptors used with the recognizer(s)
* @return a pointer to gesture descriptor
*/
static lv_indev_gesture_t * init_gesture_info(void)
{
lv_indev_gesture_t * info;
uint8_t i;
info = lv_malloc_zeroed(sizeof(lv_indev_gesture_t));
LV_ASSERT_MALLOC(info);
info->scale = 1;
info->rotation = 0.0;
for(i = 0; i < LV_GESTURE_MAX_POINTS; i++) {
info->motions[i].finger = -1;
}
return info;
}
/**
* Obtains the contact point motion descriptor with id
* @param id the id of the contact point
* @param info a pointer to the gesture descriptor that stores the motion of each contact point
* @return a pointer to the motion descriptor or NULL if not found
*/
static lv_indev_gesture_motion_t * get_motion(uint8_t id, lv_indev_gesture_t * info)
{
uint8_t i;
for(i = 0; i < LV_GESTURE_MAX_POINTS; i++) {
if(info->motions[i].finger == id) {
return &info->motions[i];
}
}
return NULL;
}
/**
* Obtains the index of the contact point motion descriptor
* @param id the id of the contact point
* @param info a pointer to the gesture descriptor that stores the motion of each contact point
* @return the index of the motion descriptor or -1 if not found
*/
static int8_t get_motion_idx(uint8_t id, lv_indev_gesture_t * info)
{
uint8_t i;
for(i = 0; i < LV_GESTURE_MAX_POINTS; i++) {
if(info->motions[i].finger == id) {
return i;
}
}
return -1;
}
/**
* Update the motion descriptors of a gesture
* @param touch a pointer to a touch data structure
* @param info a pointer to a gesture descriptor
*/
static void process_touch_event(lv_indev_touch_data_t * touch, lv_indev_gesture_t * info)
{
lv_indev_gesture_t * g = info;
lv_indev_gesture_motion_t * motion;
int8_t motion_idx;
int8_t len;
motion_idx = get_motion_idx(touch->id, g);
if(motion_idx == -1 && touch->state == LV_INDEV_STATE_PRESSED) {
if(g->finger_cnt >= LV_GESTURE_MAX_POINTS) {
/* Skip touch */
return;
}
/* New touch point id */
motion = &g->motions[g->finger_cnt];
motion->start_point.x = touch->point.x;
motion->start_point.y = touch->point.y;
motion->point.x = touch->point.x;
motion->point.y = touch->point.y;
motion->finger = touch->id;
motion->state = touch->state;
g->finger_cnt++;
}
else if(motion_idx >= 0 && touch->state == LV_INDEV_STATE_RELEASED) {
if(motion_idx == g->finger_cnt - 1) {
/* Mark last item as un-used */
motion = get_motion(touch->id, g);
if(motion == NULL) {
LV_LOG_ERROR("Released touch not found (id=%d)", touch->id);
return;
}
motion->finger = -1;
motion->state = touch->state;
}
else {
/* Move back by one */
len = (g->finger_cnt - 1) - motion_idx;
if(len > 0) {
lv_memmove(g->motions + motion_idx,
g->motions + motion_idx + 1,
sizeof(lv_indev_gesture_motion_t) * len);
}
if(g->finger_cnt > 0) {
g->motions[g->finger_cnt - 1].finger = -1;
}
LV_ASSERT(g->motions[motion_idx + 1].finger == -1);
}
if(g->finger_cnt > 0) {
g->finger_cnt--;
}
else {
g->finger_cnt = 0;
}
}
else if(motion_idx >= 0) {
motion = get_motion(touch->id, g);
if(motion == NULL) {
LV_LOG_ERROR("Active touch missing (id=%d state=%d)", touch->id, touch->state);
return;
}
motion->point.x = touch->point.x;
motion->point.y = touch->point.y;
motion->state = touch->state;
}
else {
LV_LOG_TRACE("Ignore extra touch id: %d", touch->id);
}
}
/**
* Calculate the center point of a gesture, called when there
* is a probability for the gesture to occur
* @param touch a pointer to a touch data structure
* @param touch_points_nb The number of contact point to take into account
*/
static void gesture_update_center_point(lv_indev_gesture_t * gesture, int touch_points_nb)
{
lv_indev_gesture_motion_t * motion;
lv_indev_gesture_t * g = gesture;
int32_t x = 0;
int32_t y = 0;
uint8_t i;
float scale_factor = 0.0f;
float delta_x[LV_GESTURE_MAX_POINTS] = {0.0f};
float delta_y[LV_GESTURE_MAX_POINTS] = {0.0f};
uint8_t touch_cnt = 0;
x = y = 0;
g->p_scale = g->scale;
g->p_delta_x = g->delta_x;
g->p_delta_y = g->delta_y;
g->p_rotation = g->rotation;
for(i = 0; i < touch_points_nb; i++) {
motion = &g->motions[i];
if(motion->finger >= 0) {
x += motion->point.x;
y += motion->point.y;
touch_cnt++;
}
else {
break;
}
}
g->center.x = x / touch_cnt;
g->center.y = y / touch_cnt;
for(i = 0; i < touch_points_nb; i++) {
motion = &g->motions[i];
if(motion->finger >= 0) {
delta_x[i] = motion->point.x - g->center.x;
delta_y[i] = motion->point.y - g->center.y;
scale_factor += (delta_x[i] * delta_x[i]) + (delta_y[i] * delta_y[i]);
}
}
for(i = 0; i < touch_points_nb; i++) {
motion = &g->motions[i];
if(motion->finger >= 0) {
g->scale_factors_x[i] = delta_x[i] / scale_factor;
g->scale_factors_y[i] = delta_y[i] / scale_factor;
}
}
}
/**
* Calculate the scale, translation and rotation of a gesture, called when
* the gesture has been recognized
* @param gesture a pointer to the gesture descriptor
* @param touch_points_nb the number of contact points to take into account
*/
static void gesture_calculate_factors(lv_indev_gesture_t * gesture, int touch_points_nb)
{
lv_indev_gesture_motion_t * motion;
lv_indev_gesture_t * g = gesture;
float center_x = 0;
float center_y = 0;
float a = 0;
float b = 0;
float d_x;
float d_y;
int8_t i;
int8_t touch_cnt = 0;
for(i = 0; i < touch_points_nb; i++) {
motion = &g->motions[i];
if(motion->finger >= 0) {
center_x += motion->point.x;
center_y += motion->point.y;
touch_cnt++;
}
else {
break;
}
}
center_x = center_x / touch_cnt;
center_y = center_y / touch_cnt;
/* translation */
g->delta_x = g->p_delta_x + (center_x - g->center.x);
g->delta_y = g->p_delta_y + (center_y - g->center.y);
/* rotation & scaling */
for(i = 0; i < touch_points_nb; i++) {
motion = &g->motions[i];
if(motion->finger >= 0) {
d_x = (motion->point.x - center_x);
d_y = (motion->point.y - center_y);
a += g->scale_factors_x[i] * d_x + g->scale_factors_y[i] * d_y;
b += g->scale_factors_x[i] * d_y - g->scale_factors_y[i] * d_x;
}
}
g->rotation = g->p_rotation + atan2f(b, a);
g->scale = g->p_scale * sqrtf((a * a) + (b * b));
}
/**
* Get the type of the first gesture which reports either a LV_INDEV_GESTURE_STATE_RECOGNIZED
* or LV_INDEV_GESTURE_STATE_ENDED state
* @param indev pointer to the indev device from which we want to check the gestures states
* @return the type of the gesture having the state LV_INDEV_GESTURE_STATE_RECOGNIZED or
* LV_INDEV_GESTURE_STATE_ENDED, if found
* LV_INDEV_GESTURE_NONE otherwise
*/
static lv_indev_gesture_type_t get_first_recognized_or_ended_gesture(lv_indev_t * indev)
{
for(int i = 0; i < LV_INDEV_GESTURE_CNT; i++) {
if(indev->recognizers[i].state == LV_INDEV_GESTURE_STATE_RECOGNIZED ||
indev->recognizers[i].state == LV_INDEV_GESTURE_STATE_ENDED)
return (lv_indev_gesture_type_t) i;
}
return LV_INDEV_GESTURE_NONE;
}
#endif /* LV_USE_GESTURE_RECOGNITION */
+244
View File
@@ -0,0 +1,244 @@
/*******************************************************************
*
* @file lv_indev_gesture.h
*
* Copyright (c) 2024 EDGEMTech Ltd.
*
* Author EDGEMTech Ltd, (erik.tagirov@edgemtech.ch)
*
******************************************************************/
#ifndef LV_INDEV_GESTURE_H
#define LV_INDEV_GESTURE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../core/lv_obj.h"
#if LV_USE_GESTURE_RECOGNITION
#if LV_USE_FLOAT == 0
#error "LV_USE_FLOAT is required for gesture detection."
#endif
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/* Opaque types defined in the private header */
struct lv_indev_gesture;
struct lv_indev_gesture_configuration;
typedef struct lv_indev_gesture_recognizer lv_indev_gesture_recognizer_t;
typedef struct lv_indev_touch_data lv_indev_touch_data_t;
typedef struct lv_indev_gesture lv_indev_gesture_t;
typedef struct lv_indev_gesture_configuration lv_indev_gesture_configuration_t;
typedef void (*lv_recognizer_func_t)(lv_indev_gesture_recognizer_t *, lv_indev_touch_data_t *, uint16_t);
/* The states of a gesture recognizer */
typedef enum {
LV_INDEV_GESTURE_STATE_NONE = 0, /* Beginning & end */
LV_INDEV_GESTURE_STATE_ONGOING, /* Set when there is a probability */
LV_INDEV_GESTURE_STATE_RECOGNIZED, /* Recognized, the event will contain touch info */
LV_INDEV_GESTURE_STATE_ENDED, /* A recognized gesture has ended */
LV_INDEV_GESTURE_STATE_CANCELED, /* Canceled - usually a finger is lifted */
} lv_indev_gesture_state_t;
/* Data structures for touch events - used to repsensent a libinput event */
/* Emitted by devices capable of tracking identifiable contacts (type B) */
struct lv_indev_touch_data {
lv_point_t point; /* Coordinates of the touch */
lv_indev_state_t state; /* The state i.e PRESSED or RELEASED */
uint8_t id; /* Identification/slot of the contact point */
uint32_t timestamp; /* Timestamp in milliseconds */
};
/* Gesture recognizer */
struct lv_indev_gesture_recognizer {
lv_indev_gesture_type_t type; /* The detected gesture type */
lv_indev_gesture_state_t state; /* The gesture state ongoing, recognized */
lv_indev_gesture_t * info; /* Information on the motion of each touch point */
float scale; /* Relevant for the pinch gesture */
float rotation; /* Relevant for rotation */
float distance; /* Relevant for swipes */
float speed;
lv_dir_t two_fingers_swipe_dir; /* Relevant for swipes */
lv_indev_gesture_configuration_t * config; /* The recognizer config, containing the gestures
thresholds */
lv_recognizer_func_t recog_fn; /* The recognizer function that this recongnizer must execute */
};
/**********************
* GLOBAL PROTOTYPES
**********************/
/* PINCH Gesture */
/**
* Pinch gesture recognizer function
* Will update the recognizer data
* @param recognizer pointer to a gesture recognizer
* @param touches pointer to the first element of the collected touch events
* @param touch_cnt length of passed touch event array.
*/
void lv_indev_gesture_detect_pinch(lv_indev_gesture_recognizer_t * recognizer, lv_indev_touch_data_t * touches,
uint16_t touch_cnt);
/**
* Rotation gesture recognizer function
* Will update the recognizer data
* @param recognizer pointer to a gesture recognizer
* @param touches pointer to the first element of the collected touch events
* @param touch_cnt length of passed touch event array.
*/
void lv_indev_gesture_detect_rotation(lv_indev_gesture_recognizer_t * recognizer, lv_indev_touch_data_t * touches,
uint16_t touch_cnt);
/**
* Two finger swipe gesture recognizer function
* Will update the recognizer data
* @param recognizer pointer to a gesture recognizer
* @param touches pointer to the first element of the collected touch events
* @param touch_cnt length of passed touch event array.
*/
void lv_indev_gesture_detect_two_fingers_swipe(lv_indev_gesture_recognizer_t * recognizer,
lv_indev_touch_data_t * touches,
uint16_t touch_cnt);
/**
* Set the threshold for the pinch gesture scale up, when the scale factor of gesture
* reaches the threshold events get sent
* @param indev pointer to the indev device containing the pinch recognizer
* @param threshold threshold for a pinch up gesture to be recognized
*/
void lv_indev_set_pinch_up_threshold(lv_indev_t * indev, float threshold);
/**
* Set the threshold for the pinch gesture scale down, when the scale factor of gesture
* reaches the threshold events get sent
* @param indev pointer to the indev device containing the pinch recognizer
* @param threshold threshold for a pinch down gesture to be recognized
*/
void lv_indev_set_pinch_down_threshold(lv_indev_t * indev, float threshold);
/**
* Set the rotation threshold in radian for the rotation gesture
* @param indev pointer to the indev device containing the rotation recognizer
* @param threshold threshold in radian for a rotation gesture to be recognized
*/
void lv_indev_set_rotation_rad_threshold(lv_indev_t * indev, float threshold);
/**
* Obtains the current scale of a pinch gesture
* @param gesture_event pointer to a gesture event
* @return the scale of the current gesture
*/
float lv_event_get_pinch_scale(lv_event_t * gesture_event);
/**
* Obtains the current angle in radian of a rotation gesture
* @param gesture_event pointer to a gesture event
* @return the rotation angle in radian of the current gesture
*/
float lv_event_get_rotation(lv_event_t * gesture_event);
/**
* Obtains the current distance in pixels of a two fingers swipe gesture, from the starting center
* @param gesture_event pointer to a gesture event
* @return the distance from the center, in pixels, of the current gesture
*/
float lv_event_get_two_fingers_swipe_distance(lv_event_t * gesture_event);
/**
* Obtains the current direction from the center of a two finger swipe
* @param gesture_event pointer to a gesture event
* @return the rotation angle in radian of the current gesture
*/
lv_dir_t lv_event_get_two_fingers_swipe_dir(lv_event_t * gesture_event);
/**
* Sets the state of the recognizer to a indev data structure,
* it is usually called from the indev read callback
* @param data the indev data
* @param recognizer pointer to a gesture recognizer
*/
void lv_indev_set_gesture_data(lv_indev_data_t * data, lv_indev_gesture_recognizer_t * recognizer,
lv_indev_gesture_type_t type);
/**
* Obtains the center point of a gesture
* @param gesture_event pointer to a gesture recognizer event
* @param point pointer to a point
*/
void lv_indev_get_gesture_center_point(lv_indev_gesture_recognizer_t * recognizer, lv_point_t * point);
/**
* Obtains the current state of the gesture recognizer attached to an event
* @param gesture_event pointer to a gesture recognizer event
* @return current state of the gesture recognizer
*/
lv_indev_gesture_state_t lv_event_get_gesture_state(lv_event_t * gesture_event, lv_indev_gesture_type_t type);
/**
* Obtains the current event type of the gesture recognizer attached to an event
* @param gesture_event pointer to a gesture recognizer event
* @return current event type of the gesture recognizer
*/
lv_indev_gesture_type_t lv_event_get_gesture_type(lv_event_t * gesture_event);
/**
* Obtains the coordinates of the current primary point
* @param recognizer pointer to a gesture recognizer
* @param point pointer to a point
*/
void lv_indev_get_gesture_primary_point(lv_indev_gesture_recognizer_t * recognizer, lv_point_t * point);
/**
* Allows to determine if there is an are ongoing gesture
* @param recognizer pointer to a gesture recognizer
* @return false if there are no contact points, or the gesture has ended - true otherwise
*/
bool lv_indev_recognizer_is_active(lv_indev_gesture_recognizer_t * recognizer);
/**
* Update the recognizers. It execute the recognizers functions and checks for
* LV_GESTURE_STATE_RECOGNIZED or LV_GESTURE_STATE_ENDED gestures.
* To be called in the indev read_cb.
* @param indev pointer to the indev containing from which the reconizer need an update
* @param touches indev touch data array, containing the last touch data from indev
* since the last recognizers update
* @param touch_cnt number of indev touch data in touches
*/
void lv_indev_gesture_recognizers_update(lv_indev_t * indev, lv_indev_touch_data_t * touches, uint16_t touch_cnt);
/**
* Set the lv_indev_data_t struct from the recognizer data.
* To be called in the indev read_cb.
*/
void lv_indev_gesture_recognizers_set_data(lv_indev_t * indev, lv_indev_data_t * data);
/**********************
* MACROS
**********************/
#endif /* END LV_USE_RECOGNITION */
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /* END LV_INDEV_GESTURE_H */
@@ -0,0 +1,97 @@
/*******************************************************************
*
* @file lv_indev_gesture_private.h
*
* Contains declarations and definition that are internal
* to the gesture detection logic
*
* Copyright (c) 2024 EDGEMTech Ltd.
*
* Author EDGEMTech Ltd, (erik.tagirov@edgemtech.ch)
*
******************************************************************/
#ifndef LV_INDEV_GESTURE_PRIVATE_H
#define LV_INDEV_GESTURE_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../core/lv_obj.h"
#if LV_USE_GESTURE_RECOGNITION
/*********************
* DEFINES
*********************/
#define LV_GESTURE_MAX_POINTS 2
/**********************
* TYPEDEFS
**********************/
/* Represent the motion of a finger */
struct lv_indev_gesture_motion {
int8_t finger; /* The ID of the tracked finger */
lv_point_t start_point; /* The coordinates where the DOWN event occurred */
lv_point_t point; /* The current coordinates */
lv_indev_state_t state; /* DEBUG: The state i.e PRESSED or RELEASED */
};
typedef struct lv_indev_gesture_motion lv_indev_gesture_motion_t;
/* General descriptor for a gesture, used by recognizer state machines to track
* the scale, rotation, and translation NOTE: (this will likely become private) */
struct lv_indev_gesture {
/* Motion descriptor, stores the coordinates and velocity of a contact point */
lv_indev_gesture_motion_t motions[LV_GESTURE_MAX_POINTS];
lv_point_t center; /* Center point */
float scale; /* Scale factor & previous scale factor */
float p_scale;
float scale_factors_x[LV_GESTURE_MAX_POINTS]; /* Scale factor relative to center for each point */
float scale_factors_y[LV_GESTURE_MAX_POINTS];
float delta_x; /* Translation & previous translation */
float delta_y;
float p_delta_x;
float p_delta_y;
float rotation; /* Rotation & previous rotation*/
float p_rotation;
uint8_t finger_cnt; /* Current number of contact points */
};
/* Recognizer configuration. It stores the thresholds needed to detect the gestures and
* consider them as recognized. Once recognized, indev start sending LV_GESTURE event
*/
struct lv_indev_gesture_configuration {
float pinch_up_threshold; /* Threshold for the pinch up gesture to be recognized - in pixels */
float pinch_down_threshold; /* Threshold for the pinch down gesture to be recognized - in pixels */
float rotation_angle_rad_threshold; /* Threshold for the rotation gesture to be recognized - in radians */
};
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* MACROS
**********************/
#endif /* END LV_USE_RECOGNITION */
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /* END LV_INDEV_GESTURE_PRIVATE_H */
+150
View File
@@ -0,0 +1,150 @@
/**
* @file lv_indev_private.h
*
*/
#ifndef LV_INDEV_PRIVATE_H
#define LV_INDEV_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_indev.h"
#include "../misc/lv_anim.h"
#include "lv_indev_scroll.h"
#include "lv_indev_gesture.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
struct _lv_indev_t {
/** Input device type*/
lv_indev_type_t type;
/** Function pointer to read input device data.*/
lv_indev_read_cb_t read_cb;
lv_indev_state_t state; /**< Current state of the input device.*/
lv_indev_state_t prev_state; /**< Previous state of the input device.*/
lv_indev_mode_t mode;
/*Flags*/
uint8_t long_pr_sent : 1;
uint8_t reset_query : 1;
uint8_t enabled : 1;
uint8_t wait_until_release : 1;
uint8_t stop_processing_query : 1;
uint32_t pr_timestamp; /**< Pressed time stamp*/
uint32_t longpr_rep_timestamp; /**< Long press repeat time stamp*/
void * driver_data;
void * user_data;
/**< Pointer to the assigned display*/
lv_display_t * disp;
/**< Timer to periodically read the input device*/
lv_timer_t * read_timer;
/**< Number of pixels to slide before actually drag the object*/
uint8_t scroll_limit;
/**< Drag throw slow-down in [%]. Greater value means faster slow-down*/
uint8_t scroll_throw;
/**< At least this difference should be between two points to evaluate as gesture*/
uint8_t gesture_min_velocity;
/**< At least this difference should be to send a gesture*/
uint8_t gesture_limit;
/**< Long press time in milliseconds*/
uint16_t long_press_time;
/**< Repeated trigger period in long press [ms]*/
uint16_t long_press_repeat_time;
/**< Rotary diff count will be multiplied by this value and divided by 256*/
int32_t rotary_sensitivity;
struct {
/*Pointer and button data*/
lv_point_t act_point; /**< Current point of input device.*/
lv_point_t last_point; /**< Last point of input device.*/
lv_point_t last_raw_point; /**< Last point read from read_cb. */
lv_point_t vect; /**< Difference between `act_point` and `last_point`.*/
lv_point_t scroll_sum; /*Count the dragged pixels to check LV_INDEV_DEF_SCROLL_LIMIT*/
lv_point_t scroll_throw_vect;
lv_point_t scroll_throw_vect_ori;
lv_obj_t * act_obj; /*The object being pressed*/
lv_obj_t * last_obj; /*The last object which was pressed*/
lv_obj_t * scroll_obj; /*The object being scrolled*/
lv_obj_t * last_pressed; /*The lastly pressed object*/
lv_obj_t * last_hovered; /*The lastly hovered object*/
lv_area_t scroll_area;
lv_point_t gesture_sum; /*Count the gesture pixels to check LV_INDEV_DEF_GESTURE_LIMIT*/
int32_t diff;
/*Short click streaks*/
uint8_t short_click_streak;
lv_point_t last_short_click_point;
uint32_t last_short_click_timestamp;
/*Flags*/
uint8_t scroll_dir : 4;
uint8_t gesture_dir : 4;
uint8_t gesture_sent : 1;
uint8_t press_moved : 1;
uint8_t pressed : 1;
} pointer;
struct {
/*Keypad data*/
lv_indev_state_t last_state;
uint32_t last_key;
} keypad;
lv_obj_t * cursor; /**< Cursor for LV_INPUT_TYPE_POINTER*/
lv_group_t * group; /**< Keypad destination group*/
const lv_point_t * btn_points; /**< Array points assigned to the button ()screen will be pressed
here by the buttons*/
lv_event_list_t event_list;
lv_anim_t * scroll_throw_anim;
#if LV_USE_GESTURE_RECOGNITION
lv_indev_gesture_recognizer_t recognizers[LV_INDEV_GESTURE_CNT];
lv_indev_gesture_type_t cur_gesture;
void * gesture_data[LV_INDEV_GESTURE_CNT];
lv_indev_gesture_type_t gesture_type[LV_INDEV_GESTURE_CNT];
#endif
};
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Find a scrollable object based on the current scroll vector in the indev.
* In handles scroll propagation to the parent if needed, and scroll directions too.
* @param indev pointer to an indev
* @return the found scrollable object or NULL if not found.
*/
lv_obj_t * lv_indev_find_scroll_obj(lv_indev_t * indev);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_INDEV_PRIVATE_H*/
+768
View File
@@ -0,0 +1,768 @@
/**
* @file lv_indev_scroll.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../core/lv_obj_scroll_private.h"
#include "../core/lv_obj_private.h"
#include "lv_indev.h"
#include "lv_indev_private.h"
#include "lv_indev_scroll.h"
/*********************
* DEFINES
*********************/
#define ELASTIC_SLOWNESS_FACTOR 4 /*Scrolling on elastic parts are slower by this factor*/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void init_scroll_limits(lv_indev_t * indev);
static int32_t find_snap_point_x(const lv_obj_t * obj, int32_t min, int32_t max, int32_t ofs);
static int32_t find_snap_point_y(const lv_obj_t * obj, int32_t min, int32_t max, int32_t ofs);
static void scroll_limit_diff(lv_indev_t * indev, int32_t * diff_x, int32_t * diff_y);
static int32_t elastic_diff(lv_obj_t * scroll_obj, int32_t diff, int32_t scroll_start, int32_t scroll_end,
lv_dir_t dir);
static void has_more_snap_points(lv_obj_t * scroll_obj, lv_dir_t dir, bool * has_start_snap, bool * has_end_snap);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_indev_scroll_handler(lv_indev_t * indev)
{
if(indev->pointer.vect.x == 0 && indev->pointer.vect.y == 0) {
return;
}
lv_obj_t * scroll_obj = indev->pointer.scroll_obj;
/*If there is no scroll object yet try to find one*/
if(scroll_obj == NULL) {
scroll_obj = lv_indev_find_scroll_obj(indev);
if(scroll_obj == NULL) return;
init_scroll_limits(indev);
lv_obj_remove_state(indev->pointer.act_obj, LV_STATE_PRESSED);
lv_obj_send_event(scroll_obj, LV_EVENT_SCROLL_BEGIN, NULL);
if(indev->reset_query) return;
}
/*Set new position or scroll if the vector is not zero*/
int16_t angle = 0;
int16_t scale_x = 256;
int16_t scale_y = 256;
lv_obj_t * parent = scroll_obj;
while(parent) {
angle += lv_obj_get_style_transform_rotation(parent, 0);
int32_t zoom_act_x = lv_obj_get_style_transform_scale_x_safe(parent, 0);
int32_t zoom_act_y = lv_obj_get_style_transform_scale_y_safe(parent, 0);
scale_x = (scale_x * zoom_act_x) >> 8;
scale_y = (scale_y * zoom_act_y) >> 8;
parent = lv_obj_get_parent(parent);
}
if(scale_x == 0) {
scale_x = 1;
}
if(scale_y == 0) {
scale_y = 1;
}
if(angle != 0 || scale_x != LV_SCALE_NONE || scale_y != LV_SCALE_NONE) {
angle = -angle;
scale_x = (256 * 256) / scale_x;
scale_y = (256 * 256) / scale_y;
lv_point_t pivot = { 0, 0 };
lv_point_transform(&indev->pointer.vect, angle, scale_x, scale_y, &pivot, false);
}
int32_t diff_x = 0;
int32_t diff_y = 0;
if(indev->pointer.scroll_dir == LV_DIR_HOR) {
int32_t sr = lv_obj_get_scroll_right(scroll_obj);
int32_t sl = lv_obj_get_scroll_left(scroll_obj);
diff_x = elastic_diff(scroll_obj, indev->pointer.vect.x, sl, sr, LV_DIR_HOR);
}
else {
int32_t st = lv_obj_get_scroll_top(scroll_obj);
int32_t sb = lv_obj_get_scroll_bottom(scroll_obj);
diff_y = elastic_diff(scroll_obj, indev->pointer.vect.y, st, sb, LV_DIR_VER);
}
lv_dir_t scroll_dir = lv_obj_get_scroll_dir(scroll_obj);
if((scroll_dir & LV_DIR_LEFT) == 0 && diff_x > 0) diff_x = 0;
if((scroll_dir & LV_DIR_RIGHT) == 0 && diff_x < 0) diff_x = 0;
if((scroll_dir & LV_DIR_TOP) == 0 && diff_y > 0) diff_y = 0;
if((scroll_dir & LV_DIR_BOTTOM) == 0 && diff_y < 0) diff_y = 0;
/*Respect the scroll limit area*/
scroll_limit_diff(indev, &diff_x, &diff_y);
lv_obj_scroll_by_raw(scroll_obj, diff_x, diff_y);
if(indev->reset_query) return;
indev->pointer.scroll_sum.x += diff_x;
indev->pointer.scroll_sum.y += diff_y;
}
void lv_indev_scroll_throw_handler(lv_indev_t * indev)
{
lv_obj_t * scroll_obj = indev->pointer.scroll_obj;
if(scroll_obj == NULL) return;
if(indev->pointer.scroll_dir == LV_DIR_NONE) return;
int32_t scroll_throw = indev->scroll_throw;
if(lv_obj_has_flag(scroll_obj, LV_OBJ_FLAG_SCROLL_MOMENTUM) == false) {
indev->pointer.scroll_throw_vect.y = 0;
indev->pointer.scroll_throw_vect.x = 0;
}
lv_scroll_snap_t align_x = lv_obj_get_scroll_snap_x(scroll_obj);
lv_scroll_snap_t align_y = lv_obj_get_scroll_snap_y(scroll_obj);
if(indev->pointer.scroll_dir == LV_DIR_VER) {
indev->pointer.scroll_throw_vect.x = 0;
/*If no snapping "throw"*/
if(align_y == LV_SCROLL_SNAP_NONE) {
indev->pointer.scroll_throw_vect.y =
indev->pointer.scroll_throw_vect.y * (100 - scroll_throw) / 100;
int32_t sb = lv_obj_get_scroll_bottom(scroll_obj);
int32_t st = lv_obj_get_scroll_top(scroll_obj);
indev->pointer.scroll_throw_vect.y = elastic_diff(scroll_obj, indev->pointer.scroll_throw_vect.y, st, sb,
LV_DIR_VER);
lv_obj_scroll_by_raw(scroll_obj, 0, indev->pointer.scroll_throw_vect.y);
if(indev->reset_query) return;
}
/*With snapping find the nearest snap point and scroll there*/
else {
int32_t diff_y = lv_indev_scroll_throw_predict(indev, LV_DIR_VER);
indev->pointer.scroll_throw_vect.y = 0;
scroll_limit_diff(indev, NULL, &diff_y);
int32_t y = find_snap_point_y(scroll_obj, LV_COORD_MIN, LV_COORD_MAX, diff_y);
lv_obj_scroll_by(scroll_obj, 0, diff_y + y, LV_ANIM_ON);
if(indev->reset_query) return;
}
}
else if(indev->pointer.scroll_dir == LV_DIR_HOR) {
indev->pointer.scroll_throw_vect.y = 0;
/*If no snapping "throw"*/
if(align_x == LV_SCROLL_SNAP_NONE) {
indev->pointer.scroll_throw_vect.x =
indev->pointer.scroll_throw_vect.x * (100 - scroll_throw) / 100;
int32_t sl = lv_obj_get_scroll_left(scroll_obj);
int32_t sr = lv_obj_get_scroll_right(scroll_obj);
indev->pointer.scroll_throw_vect.x = elastic_diff(scroll_obj, indev->pointer.scroll_throw_vect.x, sl, sr,
LV_DIR_HOR);
lv_obj_scroll_by_raw(scroll_obj, indev->pointer.scroll_throw_vect.x, 0);
if(indev->reset_query) return;
}
/*With snapping find the nearest snap point and scroll there*/
else {
int32_t diff_x = lv_indev_scroll_throw_predict(indev, LV_DIR_HOR);
indev->pointer.scroll_throw_vect.x = 0;
scroll_limit_diff(indev, &diff_x, NULL);
int32_t x = find_snap_point_x(scroll_obj, LV_COORD_MIN, LV_COORD_MAX, diff_x);
lv_obj_scroll_by(scroll_obj, x + diff_x, 0, LV_ANIM_ON);
if(indev->reset_query) return;
}
}
/*Check if the scroll has finished*/
if(indev->pointer.scroll_throw_vect.x == 0 && indev->pointer.scroll_throw_vect.y == 0) {
/*Revert if scrolled in*/
/*If vertically scrollable and not controlled by snap*/
if(align_y == LV_SCROLL_SNAP_NONE) {
int32_t st = lv_obj_get_scroll_top(scroll_obj);
int32_t sb = lv_obj_get_scroll_bottom(scroll_obj);
if(st > 0 || sb > 0) {
if(st < 0) {
lv_obj_scroll_by(scroll_obj, 0, st, LV_ANIM_ON);
if(indev->reset_query) return;
}
else if(sb < 0) {
lv_obj_scroll_by(scroll_obj, 0, -sb, LV_ANIM_ON);
if(indev->reset_query) return;
}
}
}
/*If horizontally scrollable and not controlled by snap*/
if(align_x == LV_SCROLL_SNAP_NONE) {
int32_t sl = lv_obj_get_scroll_left(scroll_obj);
int32_t sr = lv_obj_get_scroll_right(scroll_obj);
if(sl > 0 || sr > 0) {
if(sl < 0) {
lv_obj_scroll_by(scroll_obj, sl, 0, LV_ANIM_ON);
if(indev->reset_query) return;
}
else if(sr < 0) {
lv_obj_scroll_by(scroll_obj, -sr, 0, LV_ANIM_ON);
if(indev->reset_query) return;
}
}
}
lv_obj_send_event(scroll_obj, LV_EVENT_SCROLL_END, indev);
if(indev->reset_query) return;
indev->pointer.scroll_dir = LV_DIR_NONE;
indev->pointer.scroll_obj = NULL;
}
}
int32_t lv_indev_scroll_throw_predict(lv_indev_t * indev, lv_dir_t dir)
{
if(indev == NULL) return 0;
int32_t v;
switch(dir) {
case LV_DIR_VER:
v = indev->pointer.scroll_throw_vect_ori.y;
break;
case LV_DIR_HOR:
v = indev->pointer.scroll_throw_vect_ori.x;
break;
default:
return 0;
}
int32_t scroll_throw = indev->scroll_throw;
int32_t sum = 0;
while(v) {
sum += v;
v = v * (100 - scroll_throw) / 100;
}
return sum;
}
void lv_indev_scroll_get_snap_dist(lv_obj_t * obj, lv_point_t * p)
{
p->x = find_snap_point_x(obj, obj->coords.x1, obj->coords.x2, 0);
p->y = find_snap_point_y(obj, obj->coords.y1, obj->coords.y2, 0);
}
lv_obj_t * lv_indev_find_scroll_obj(lv_indev_t * indev)
{
lv_obj_t * obj_candidate = NULL;
lv_dir_t dir_candidate = LV_DIR_NONE;
int32_t scroll_limit = indev->scroll_limit;
/*Go until find a scrollable object in the current direction
*More precisely:
* 1. Check the pressed object and all of its ancestors and try to find an object which is scrollable
* 2. Scrollable means it has some content out of its area
* 3. If an object can be scrolled into the current direction then use it ("real match"")
* 4. If can be scrolled on the current axis (hor/ver) save it as candidate (at least show an elastic scroll effect)
* 5. Use the last candidate. Always the "deepest" parent or the object from point 3*/
lv_obj_t * obj_act = indev->pointer.act_obj;
/*Decide if it's a horizontal or vertical scroll*/
bool hor_en = false;
bool ver_en = false;
indev->pointer.scroll_sum.x += indev->pointer.vect.x;
indev->pointer.scroll_sum.y += indev->pointer.vect.y;
while(obj_act) {
/*Get the transformed scroll_sum with this object*/
int16_t angle = 0;
int32_t scale_x = 256;
int32_t scale_y = 256;
lv_point_t pivot = { 0, 0 };
lv_obj_t * parent = obj_act;
while(parent) {
angle += lv_obj_get_style_transform_rotation(parent, 0);
int32_t zoom_act_x = lv_obj_get_style_transform_scale_x_safe(parent, 0);
int32_t zoom_act_y = lv_obj_get_style_transform_scale_y_safe(parent, 0);
scale_x = (scale_x * zoom_act_x) >> 8;
scale_y = (scale_y * zoom_act_y) >> 8;
parent = lv_obj_get_parent(parent);
}
if(scale_x == 0) {
scale_x = 1;
}
if(scale_y == 0) {
scale_y = 1;
}
lv_point_t obj_scroll_sum = indev->pointer.scroll_sum;
if(angle != 0 || scale_x != LV_SCALE_NONE || scale_y != LV_SCALE_NONE) {
angle = -angle;
scale_x = (256 * 256) / scale_x;
scale_y = (256 * 256) / scale_y;
lv_point_transform(&obj_scroll_sum, angle, scale_x, scale_y, &pivot, false);
}
if(LV_ABS(obj_scroll_sum.x) > LV_ABS(obj_scroll_sum.y)) {
hor_en = true;
}
else {
ver_en = true;
}
if(lv_obj_has_flag(obj_act, LV_OBJ_FLAG_SCROLLABLE) == false) {
/*If this object don't want to chain the scroll to the parent stop searching*/
if(lv_obj_has_flag(obj_act, LV_OBJ_FLAG_SCROLL_CHAIN_HOR) == false && hor_en) break;
if(lv_obj_has_flag(obj_act, LV_OBJ_FLAG_SCROLL_CHAIN_VER) == false && ver_en) break;
obj_act = lv_obj_get_parent(obj_act);
continue;
}
/*Consider both up-down or left/right scrollable according to the current direction*/
bool up_en = ver_en;
bool down_en = ver_en;
bool left_en = hor_en;
bool right_en = hor_en;
/*The object might have disabled some directions.*/
lv_dir_t scroll_dir = lv_obj_get_scroll_dir(obj_act);
if((scroll_dir & LV_DIR_LEFT) == 0) left_en = false;
if((scroll_dir & LV_DIR_RIGHT) == 0) right_en = false;
if((scroll_dir & LV_DIR_TOP) == 0) up_en = false;
if((scroll_dir & LV_DIR_BOTTOM) == 0) down_en = false;
/*Horizontal scroll*/
int32_t sl = 0;
int32_t sr = 0;
lv_scroll_snap_t snap_x = lv_obj_get_scroll_snap_x(obj_act);
if(snap_x == LV_SCROLL_SNAP_NONE) {
sl = lv_obj_get_scroll_left(obj_act);
sr = lv_obj_get_scroll_right(obj_act);
}
else {
bool has_start_snap;
bool has_end_snap;
has_more_snap_points(obj_act, LV_DIR_HOR, &has_start_snap, &has_end_snap);
/*Assume scrolling is there are more snap point
*Assumed scroll in if there are NO more nap points*/
sl = has_start_snap ? 1 : -1;
sr = has_end_snap ? 1 : -1;
}
/*Vertical scroll*/
int32_t st = 0;
int32_t sb = 0;
lv_scroll_snap_t snap_y = lv_obj_get_scroll_snap_y(obj_act);
if(snap_y == LV_SCROLL_SNAP_NONE) {
st = lv_obj_get_scroll_top(obj_act);
sb = lv_obj_get_scroll_bottom(obj_act);
}
else {
bool has_start_snap;
bool has_end_snap;
has_more_snap_points(obj_act, LV_DIR_VER, &has_start_snap, &has_end_snap);
/*Assume scrolling is there are more snap point
*Assumed scroll in if there are NO more nap points*/
st = has_start_snap ? 1 : -1;
sb = has_end_snap ? 1 : -1;
}
/*If this object is scrollable into the current scroll direction then save it as a candidate.
*It's important only to be scrollable on the current axis (hor/ver) because if the scroll
*is propagated to this object it can show at least elastic scroll effect.
*But if not hor/ver scrollable do not scroll it at all (so it's not a good candidate)*/
if((st > 0 || sb > 0) &&
((up_en && obj_scroll_sum.y >= scroll_limit) ||
(down_en && obj_scroll_sum.y <= - scroll_limit))) {
obj_candidate = obj_act;
dir_candidate = LV_DIR_VER;
}
if((sl > 0 || sr > 0) &&
((left_en && obj_scroll_sum.x >= scroll_limit) ||
(right_en && obj_scroll_sum.x <= - scroll_limit))) {
obj_candidate = obj_act;
dir_candidate = LV_DIR_HOR;
}
if(st <= 0) up_en = false;
if(sb <= 0) down_en = false;
if(sl <= 0) left_en = false;
if(sr <= 0) right_en = false;
/*If the object really can be scrolled into the current direction then use it.*/
if((left_en && obj_scroll_sum.x >= scroll_limit) ||
(right_en && obj_scroll_sum.x <= - scroll_limit) ||
(up_en && obj_scroll_sum.y >= scroll_limit) ||
(down_en && obj_scroll_sum.y <= - scroll_limit)) {
indev->pointer.scroll_dir = hor_en ? LV_DIR_HOR : LV_DIR_VER;
break;
}
/*If this object don't want to chain the scroll to the parent stop searching*/
if(lv_obj_has_flag(obj_act, LV_OBJ_FLAG_SCROLL_CHAIN_HOR) == false && hor_en) break;
if(lv_obj_has_flag(obj_act, LV_OBJ_FLAG_SCROLL_CHAIN_VER) == false && ver_en) break;
/*Try the parent*/
obj_act = lv_obj_get_parent(obj_act);
}
/*Use the last candidate*/
if(obj_candidate) {
indev->pointer.scroll_dir = dir_candidate;
indev->pointer.scroll_obj = obj_candidate;
indev->pointer.scroll_sum.x = 0;
indev->pointer.scroll_sum.y = 0;
}
return obj_candidate;
}
/**********************
* STATIC FUNCTIONS
**********************/
static void init_scroll_limits(lv_indev_t * indev)
{
lv_obj_t * obj = indev->pointer.scroll_obj;
/*If there no STOP allow scrolling anywhere*/
if(lv_obj_has_flag(obj, LV_OBJ_FLAG_SCROLL_ONE) == false) {
lv_area_set(&indev->pointer.scroll_area, LV_COORD_MIN, LV_COORD_MIN, LV_COORD_MAX, LV_COORD_MAX);
}
/*With STOP limit the scrolling to the perv and next snap point*/
else {
switch(lv_obj_get_scroll_snap_y(obj)) {
case LV_SCROLL_SNAP_START:
indev->pointer.scroll_area.y1 = find_snap_point_y(obj, obj->coords.y1 + 1, LV_COORD_MAX, 0);
indev->pointer.scroll_area.y2 = find_snap_point_y(obj, LV_COORD_MIN, obj->coords.y1 - 1, 0);
break;
case LV_SCROLL_SNAP_END:
indev->pointer.scroll_area.y1 = find_snap_point_y(obj, obj->coords.y2, LV_COORD_MAX, 0);
indev->pointer.scroll_area.y2 = find_snap_point_y(obj, LV_COORD_MIN, obj->coords.y2, 0);
break;
case LV_SCROLL_SNAP_CENTER: {
int32_t y_mid = obj->coords.y1 + lv_area_get_height(&obj->coords) / 2;
indev->pointer.scroll_area.y1 = find_snap_point_y(obj, y_mid + 1, LV_COORD_MAX, 0);
indev->pointer.scroll_area.y2 = find_snap_point_y(obj, LV_COORD_MIN, y_mid - 1, 0);
break;
}
default:
indev->pointer.scroll_area.y1 = LV_COORD_MIN;
indev->pointer.scroll_area.y2 = LV_COORD_MAX;
break;
}
switch(lv_obj_get_scroll_snap_x(obj)) {
case LV_SCROLL_SNAP_START:
indev->pointer.scroll_area.x1 = find_snap_point_x(obj, obj->coords.x1, LV_COORD_MAX, 0);
indev->pointer.scroll_area.x2 = find_snap_point_x(obj, LV_COORD_MIN, obj->coords.x1, 0);
break;
case LV_SCROLL_SNAP_END:
indev->pointer.scroll_area.x1 = find_snap_point_x(obj, obj->coords.x2, LV_COORD_MAX, 0);
indev->pointer.scroll_area.x2 = find_snap_point_x(obj, LV_COORD_MIN, obj->coords.x2, 0);
break;
case LV_SCROLL_SNAP_CENTER: {
int32_t x_mid = obj->coords.x1 + lv_area_get_width(&obj->coords) / 2;
indev->pointer.scroll_area.x1 = find_snap_point_x(obj, x_mid + 1, LV_COORD_MAX, 0);
indev->pointer.scroll_area.x2 = find_snap_point_x(obj, LV_COORD_MIN, x_mid - 1, 0);
break;
}
default:
indev->pointer.scroll_area.x1 = LV_COORD_MIN;
indev->pointer.scroll_area.x2 = LV_COORD_MAX;
break;
}
}
/*`find_snap_point_x/y()` return LV_COORD_MAX is not snap point was found,
*but x1/y1 should be small. */
if(indev->pointer.scroll_area.x1 == LV_COORD_MAX) indev->pointer.scroll_area.x1 = LV_COORD_MIN;
if(indev->pointer.scroll_area.y1 == LV_COORD_MAX) indev->pointer.scroll_area.y1 = LV_COORD_MIN;
/*Allow scrolling on the edges. It will be reverted to the edge due to snapping anyway*/
if(indev->pointer.scroll_area.x1 == 0) indev->pointer.scroll_area.x1 = LV_COORD_MIN;
if(indev->pointer.scroll_area.x2 == 0) indev->pointer.scroll_area.x2 = LV_COORD_MAX;
if(indev->pointer.scroll_area.y1 == 0) indev->pointer.scroll_area.y1 = LV_COORD_MIN;
if(indev->pointer.scroll_area.y2 == 0) indev->pointer.scroll_area.y2 = LV_COORD_MAX;
}
/**
* Search for snap point in the min..max range.
* @param obj the object on which snap point should be found
* @param min ignore snap points smaller than this. (Absolute coordinate)
* @param max ignore snap points greater than this. (Absolute coordinate)
* @param ofs offset to snap points. Useful the get a snap point in an imagined case
* what if children are already moved by this value
* @return the absolute x coordinate of the nearest snap point
* or `LV_COORD_MAX` if there is no snap point in the min..max range
*/
static int32_t find_snap_point_x(const lv_obj_t * obj, int32_t min, int32_t max, int32_t ofs)
{
lv_scroll_snap_t align = lv_obj_get_scroll_snap_x(obj);
if(align == LV_SCROLL_SNAP_NONE) return LV_COORD_MAX;
int32_t dist = LV_COORD_MAX;
int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
int32_t pad_right = lv_obj_get_style_pad_right(obj, LV_PART_MAIN);
uint32_t i;
uint32_t child_cnt = lv_obj_get_child_count(obj);
for(i = 0; i < child_cnt; i++) {
lv_obj_t * child = obj->spec_attr->children[i];
if(lv_obj_has_flag_any(child, LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) continue;
if(lv_obj_has_flag(child, LV_OBJ_FLAG_SNAPPABLE)) {
int32_t x_child = 0;
int32_t x_parent = 0;
switch(align) {
case LV_SCROLL_SNAP_START:
x_child = child->coords.x1;
x_parent = obj->coords.x1 + pad_left;
break;
case LV_SCROLL_SNAP_END:
x_child = child->coords.x2;
x_parent = obj->coords.x2 - pad_right;
break;
case LV_SCROLL_SNAP_CENTER:
x_child = child->coords.x1 + lv_area_get_width(&child->coords) / 2;
x_parent = obj->coords.x1 + pad_left + (lv_area_get_width(&obj->coords) - pad_left - pad_right) / 2;
break;
default:
continue;
}
x_child += ofs;
if(x_child >= min && x_child <= max) {
int32_t x = x_child - x_parent;
if(LV_ABS(x) < LV_ABS(dist)) dist = x;
}
}
}
return dist == LV_COORD_MAX ? LV_COORD_MAX : -dist;
}
/**
* Search for snap point in the min..max range.
* @param obj the object on which snap point should be found
* @param min ignore snap points smaller than this. (Absolute coordinate)
* @param max ignore snap points greater than this. (Absolute coordinate)
* @param ofs offset to snap points. Useful to get a snap point in an imagined case
* what if children are already moved by this value
* @return the absolute y coordinate of the nearest snap point
* or `LV_COORD_MAX` if there is no snap point in the min..max range
*/
static int32_t find_snap_point_y(const lv_obj_t * obj, int32_t min, int32_t max, int32_t ofs)
{
lv_scroll_snap_t align = lv_obj_get_scroll_snap_y(obj);
if(align == LV_SCROLL_SNAP_NONE) return LV_COORD_MAX;
int32_t dist = LV_COORD_MAX;
int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
int32_t pad_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
uint32_t i;
uint32_t child_cnt = lv_obj_get_child_count(obj);
for(i = 0; i < child_cnt; i++) {
lv_obj_t * child = obj->spec_attr->children[i];
if(lv_obj_has_flag_any(child, LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) continue;
if(lv_obj_has_flag(child, LV_OBJ_FLAG_SNAPPABLE)) {
int32_t y_child = 0;
int32_t y_parent = 0;
switch(align) {
case LV_SCROLL_SNAP_START:
y_child = child->coords.y1;
y_parent = obj->coords.y1 + pad_top;
break;
case LV_SCROLL_SNAP_END:
y_child = child->coords.y2;
y_parent = obj->coords.y2 - pad_bottom;
break;
case LV_SCROLL_SNAP_CENTER:
y_child = child->coords.y1 + lv_area_get_height(&child->coords) / 2;
y_parent = obj->coords.y1 + pad_top + (lv_area_get_height(&obj->coords) - pad_top - pad_bottom) / 2;
break;
default:
continue;
}
y_child += ofs;
if(y_child >= min && y_child <= max) {
int32_t y = y_child - y_parent;
if(LV_ABS(y) < LV_ABS(dist)) dist = y;
}
}
}
return dist == LV_COORD_MAX ? LV_COORD_MAX : -dist;
}
static void scroll_limit_diff(lv_indev_t * indev, int32_t * diff_x, int32_t * diff_y)
{
if(diff_y) {
if(indev->pointer.scroll_sum.y + *diff_y < indev->pointer.scroll_area.y1) {
*diff_y = indev->pointer.scroll_area.y1 - indev->pointer.scroll_sum.y;
}
if(indev->pointer.scroll_sum.y + *diff_y > indev->pointer.scroll_area.y2) {
*diff_y = indev->pointer.scroll_area.y2 - indev->pointer.scroll_sum.y;
}
}
if(diff_x) {
if(indev->pointer.scroll_sum.x + *diff_x < indev->pointer.scroll_area.x1) {
*diff_x = indev->pointer.scroll_area.x1 - indev->pointer.scroll_sum.x;
}
if(indev->pointer.scroll_sum.x + *diff_x > indev->pointer.scroll_area.x2) {
*diff_x = indev->pointer.scroll_area.x2 - indev->pointer.scroll_sum.x;
}
}
}
static int32_t elastic_diff(lv_obj_t * scroll_obj, int32_t diff, int32_t scroll_start, int32_t scroll_end,
lv_dir_t dir)
{
if(diff == 0) return 0;
/*Scroll back to the edge if required*/
if(!lv_obj_has_flag(scroll_obj, LV_OBJ_FLAG_SCROLL_ELASTIC)) {
/*
* If the scrolling object does not set the `LV_OBJ_FLAG_SCROLL_ELASTIC` flag,
* make sure that `diff` will not cause the scroll to exceed the `start` or `end` boundary of the content.
* If the content has exceeded the boundary due to external factors like `LV_SCROLL_SNAP_CENTER`,
* then respect the current position instead of going straight back to 0.
*/
const int32_t scroll_ended = diff > 0 ? scroll_start : scroll_end;
if(scroll_ended < 0) diff = 0;
else if(scroll_ended - diff < 0) diff = scroll_ended;
}
/*Handle elastic scrolling*/
else {
lv_scroll_snap_t snap;
snap = dir == LV_DIR_HOR ? lv_obj_get_scroll_snap_x(scroll_obj) : lv_obj_get_scroll_snap_y(scroll_obj);
/*Without snapping just scale down the diff when scrolled out*/
if(snap == LV_SCROLL_SNAP_NONE) {
if(scroll_end < 0 || scroll_start < 0) {
/*Rounding*/
if(diff < 0) diff -= ELASTIC_SLOWNESS_FACTOR / 2;
if(diff > 0) diff += ELASTIC_SLOWNESS_FACTOR / 2;
return diff / ELASTIC_SLOWNESS_FACTOR;
}
else {
return diff;
}
}
/*With snapping the widget is scrolled out if there are no more snap points
*at least in one direction (start or end)*/
bool has_start_snap;
bool has_end_snap;
has_more_snap_points(scroll_obj, dir, &has_start_snap, &has_end_snap);
if(!has_start_snap || !has_end_snap) {
/*Rounding*/
if(diff < 0) diff -= ELASTIC_SLOWNESS_FACTOR / 2;
if(diff > 0) diff += ELASTIC_SLOWNESS_FACTOR / 2;
return diff / ELASTIC_SLOWNESS_FACTOR;
}
else {
return diff;
}
}
return diff;
}
/**
* Tell is there are more snap point in a given direction considering snap position.
* There is a snap point if there is a snapanble object in the given direction
* @param scroll_obj the object on which snap points should be found
* @param dir LV_DIR_HOR or LV_DIR_VER
* @param has_start_snap true: there is snap point in the start direction (top or left depending on dir)
* @param has_end_snap true: there is snap point in the end direction (bottom or right depending on dir)
* @note snap points will be searched relative to the
* center point in case of LV_SCROLL_SNAP_CENTER
* start point (top or left) in case of LV_SCROLL_SNAP_START
* end point (bottom or right) in case of LV_SCROLL_SNAP_END
*/
static void has_more_snap_points(lv_obj_t * scroll_obj, lv_dir_t dir, bool * has_start_snap, bool * has_end_snap)
{
*has_start_snap = true;
*has_end_snap = true;
lv_scroll_snap_t snap;
snap = dir == LV_DIR_HOR ? lv_obj_get_scroll_snap_x(scroll_obj) : lv_obj_get_scroll_snap_y(scroll_obj);
if(dir == LV_DIR_HOR) {
int32_t x = 0;
switch(snap) {
case LV_SCROLL_SNAP_CENTER: {
int32_t pad_left = lv_obj_get_style_pad_left(scroll_obj, 0);
int32_t pad_right = lv_obj_get_style_pad_right(scroll_obj, 0);
x = scroll_obj->coords.x1;
x += (lv_area_get_width(&scroll_obj->coords) - pad_left - pad_right) / 2;
x += pad_left;
}
break;
case LV_SCROLL_SNAP_START:
x = scroll_obj->coords.x1 + lv_obj_get_style_pad_left(scroll_obj, 0);
break;
case LV_SCROLL_SNAP_END:
x = scroll_obj->coords.x2 - lv_obj_get_style_pad_right(scroll_obj, 0);
break;
default:
break;
}
int32_t d;
d = find_snap_point_x(scroll_obj, x + 1, LV_COORD_MAX, 0);
if(d == LV_COORD_MAX) *has_end_snap = false;
d = find_snap_point_x(scroll_obj, LV_COORD_MIN, x - 1, 0);
if(d == LV_COORD_MAX) *has_start_snap = false;
}
else {
int32_t y = 0;
switch(snap) {
case LV_SCROLL_SNAP_CENTER: {
int32_t pad_top = lv_obj_get_style_pad_top(scroll_obj, 0);
int32_t pad_bottom = lv_obj_get_style_pad_bottom(scroll_obj, 0);
y = scroll_obj->coords.y1;
y += (lv_area_get_height(&scroll_obj->coords) - pad_top - pad_bottom) / 2;
y += pad_top;
}
break;
case LV_SCROLL_SNAP_START:
y = scroll_obj->coords.y1 + lv_obj_get_style_pad_top(scroll_obj, 0);
break;
case LV_SCROLL_SNAP_END:
y = scroll_obj->coords.y2 - lv_obj_get_style_pad_bottom(scroll_obj, 0);
break;
default:
break;
}
int32_t d;
d = find_snap_point_y(scroll_obj, y + 1, LV_COORD_MAX, 0);
if(d == LV_COORD_MAX) *has_end_snap = false;
d = find_snap_point_y(scroll_obj, LV_COORD_MIN, y - 1, 0);
if(d == LV_COORD_MAX) *has_start_snap = false;
}
}
+65
View File
@@ -0,0 +1,65 @@
/**
* @file lv_indev_scroll.h
*
*/
#ifndef LV_INDEV_SCROLL_H
#define LV_INDEV_SCROLL_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../core/lv_obj.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Handle scrolling. Called by LVGL during input device processing
* @param indev pointer to an input device
*/
void lv_indev_scroll_handler(lv_indev_t * indev);
/**
* Handle throwing after scrolling. Called by LVGL during input device processing
* @param indev pointer to an input device
*/
void lv_indev_scroll_throw_handler(lv_indev_t * indev);
/**
* Predict where would a scroll throw end
* @param indev pointer to an input device
* @param dir ` LV_DIR_VER` or `LV_DIR_HOR`
* @return the difference compared to the current position when the throw would be finished
*/
int32_t lv_indev_scroll_throw_predict(lv_indev_t * indev, lv_dir_t dir);
/**
* Get the distance of the nearest snap point
* @param obj the object on which snap points should be found
* @param p save the distance of the found snap point there
*/
void lv_indev_scroll_get_snap_dist(lv_obj_t * obj, lv_point_t * p);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_INDEV_SCROLL_H*/