Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(indev): detect double and triple click (closes #6020) #6187

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 5 additions & 2 deletions docs/overview/event.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,13 @@ Input device events
- :cpp:enumerator:`LV_EVENT_PRESSED`: The object has been pressed
- :cpp:enumerator:`LV_EVENT_PRESSING`: The object is being pressed (called continuously while pressing)
- :cpp:enumerator:`LV_EVENT_PRESS_LOST`: The object is still being pressed but slid cursor/finger off of the object
- :cpp:enumerator:`LV_EVENT_SHORT_CLICKED`: The object was pressed for a short period of time, then released it. Not called if scrolled.
- :cpp:enumerator:`LV_EVENT_SHORT_CLICKED`: The object was pressed for a short period of time, and then released without scrolling.
- :cpp:enumerator:`LV_EVENT_SINGLE_CLICKED`: The object was pressed for a short period of time, and then released without scrolling, for the first time in a click streak. A click streak refers to multiple short clicks within a short period of time and a small distance.
- :cpp:enumerator:`LV_EVENT_DOUBLE_CLICKED`: The object was pressed for a short period of time, and then released without scrolling, for the second time in a click streak.
- :cpp:enumerator:`LV_EVENT_TRIPLE_CLICKED`: The object was pressed for a short period of time, and then released without scrolling, for the third time in a click streak.
- :cpp:enumerator:`LV_EVENT_LONG_PRESSED`: Object has been pressed for at least `long_press_time`. Not called if scrolled.
- :cpp:enumerator:`LV_EVENT_LONG_PRESSED_REPEAT`: Called after `long_press_time` in every `long_press_repeat_time` ms. Not called if scrolled.
- :cpp:enumerator:`LV_EVENT_CLICKED`: Called on release if not scrolled (regardless to long press)
- :cpp:enumerator:`LV_EVENT_CLICKED`: Called on release if not scrolled (regardless of long press)
- :cpp:enumerator:`LV_EVENT_RELEASED`: Called in every cases when the object has been released
- :cpp:enumerator:`LV_EVENT_SCROLL_BEGIN`: Scrolling begins. The event parameter is a pointer to the animation of the scroll. Can be modified
- :cpp:enumerator:`LV_EVENT_SCROLL_THROW_BEGIN`:
Expand Down
40 changes: 36 additions & 4 deletions src/indev/lv_indev.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ static void indev_encoder_proc(lv_indev_t * i, lv_indev_data_t * data);
static void indev_button_proc(lv_indev_t * i, lv_indev_data_t * data);
static void indev_proc_press(lv_indev_t * indev);
static void indev_proc_release(lv_indev_t * indev);
static lv_result_t indev_proc_short_click(lv_indev_t * indev);
static void indev_proc_pointer_diff(lv_indev_t * indev);
static lv_obj_t * pointer_search_obj(lv_display_t * disp, lv_point_t * p);
static void indev_proc_reset_query_handler(lv_indev_t * indev);
Expand Down Expand Up @@ -814,7 +815,7 @@ static void indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data)
if(send_event(LV_EVENT_RELEASED, indev_act) == LV_RESULT_INVALID) return;

if(i->long_pr_sent == 0) {
if(send_event(LV_EVENT_SHORT_CLICKED, indev_act) == LV_RESULT_INVALID) return;
if(indev_proc_short_click(i) == LV_RESULT_INVALID) return;
}

if(send_event(LV_EVENT_CLICKED, indev_act) == LV_RESULT_INVALID) return;
Expand All @@ -826,6 +827,8 @@ static void indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data)
indev_obj_act = NULL;
}



/**
* Process a new point from LV_INDEV_TYPE_ENCODER input device
* @param i pointer to an input device
Expand Down Expand Up @@ -977,7 +980,7 @@ static void indev_encoder_proc(lv_indev_t * i, lv_indev_data_t * data)
}

if(i->long_pr_sent == 0 && is_enabled) {
if(send_event(LV_EVENT_SHORT_CLICKED, indev_act) == LV_RESULT_INVALID) return;
if(indev_proc_short_click(i) == LV_RESULT_INVALID) return;
}

if(is_enabled) {
Expand All @@ -991,7 +994,7 @@ static void indev_encoder_proc(lv_indev_t * i, lv_indev_data_t * data)
if(!i->long_pr_sent || lv_group_get_obj_count(g) <= 1) {
if(is_enabled) {
if(send_event(LV_EVENT_RELEASED, indev_act) == LV_RESULT_INVALID) return;
if(send_event(LV_EVENT_SHORT_CLICKED, indev_act) == LV_RESULT_INVALID) return;
if(indev_proc_short_click(i) == LV_RESULT_INVALID) return;
if(send_event(LV_EVENT_CLICKED, indev_act) == LV_RESULT_INVALID) return;
}

Expand Down Expand Up @@ -1315,7 +1318,7 @@ static void indev_proc_release(lv_indev_t * indev)
if(is_enabled) {
if(scroll_obj == NULL) {
if(indev->long_pr_sent == 0) {
if(send_event(LV_EVENT_SHORT_CLICKED, indev_act) == LV_RESULT_INVALID) return;
if(indev_proc_short_click(indev) == LV_RESULT_INVALID) return;
}
if(send_event(LV_EVENT_CLICKED, indev_act) == LV_RESULT_INVALID) return;
}
Expand Down Expand Up @@ -1362,6 +1365,35 @@ static void indev_proc_release(lv_indev_t * indev)
}
}

static lv_result_t indev_proc_short_click(lv_indev_t * indev)
{
/*Simple short click*/
lv_result_t res = send_event(LV_EVENT_SHORT_CLICKED, indev_act);
if(res == LV_RESULT_INVALID) {
return res;
}

/*Update streak for clicks within small distance and short time*/
int32_t dx = indev->pointer.last_short_click_point.x - indev->pointer.act_point.x;
int32_t dy = indev->pointer.last_short_click_point.y - indev->pointer.act_point.y;
if(dx * dx + dy * dy > indev->scroll_limit * indev->scroll_limit ||
lv_tick_elaps(indev->pointer.last_short_click_timestamp) > indev->long_press_time) {
indev->pointer.short_click_streak = 0;
}
else {
indev->pointer.short_click_streak = (indev->pointer.short_click_streak + 1) % 3;
}
indev->pointer.last_short_click_timestamp = lv_tick_get();
indev->pointer.last_short_click_point = indev->pointer.act_point;

/*Fire single/double/triple click*/
if(indev->pointer.short_click_streak == 0) send_event(LV_EVENT_SINGLE_CLICKED, indev_act);
if(indev->pointer.short_click_streak == 1) send_event(LV_EVENT_DOUBLE_CLICKED, indev_act);
if(indev->pointer.short_click_streak == 2) send_event(LV_EVENT_TRIPLE_CLICKED, indev_act);

return res;
}

static void indev_proc_pointer_diff(lv_indev_t * indev)
{
lv_obj_t * obj = indev->pointer.last_pressed;
Expand Down
5 changes: 4 additions & 1 deletion src/indev/lv_indev_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ struct _lv_indev_t {
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 : 2;
lv_point_t last_short_click_point;
uint32_t last_short_click_timestamp;
/*Flags*/
lv_dir_t scroll_dir : 4;
lv_dir_t gesture_dir : 4;
Expand Down
3 changes: 3 additions & 0 deletions src/misc/lv_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ typedef enum {
LV_EVENT_PRESSING, /**< The object is being pressed (called continuously while pressing)*/
LV_EVENT_PRESS_LOST, /**< The object is still being pressed but slid cursor/finger off of the object */
LV_EVENT_SHORT_CLICKED, /**< The object was pressed for a short period of time, then released it. Not called if scrolled.*/
LV_EVENT_SINGLE_CLICKED, /**< Called for the first short click within a small distance and short time*/
LV_EVENT_DOUBLE_CLICKED, /**< Called for the second short click within small distance and short time*/
LV_EVENT_TRIPLE_CLICKED, /**< Called for the third short click within small distance and short time*/
LV_EVENT_LONG_PRESSED, /**< Object has been pressed for at least `long_press_time`. Not called if scrolled.*/
LV_EVENT_LONG_PRESSED_REPEAT, /**< Called after `long_press_time` in every `long_press_repeat_time` ms. Not called if scrolled.*/
LV_EVENT_CLICKED, /**< Called on release if not scrolled (regardless to long press)*/
Expand Down
151 changes: 151 additions & 0 deletions tests/src/test_cases/test_click.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#if LV_BUILD_TEST
#include "../lvgl.h"
#include "../lv_test_indev.h"
#include "unity/unity.h"

void setUp(void)
{
/* Function run before every test */
}

void tearDown(void)
{
/* Function run after every test */
lv_obj_clean(lv_screen_active());
}

struct click_counts {
uint32_t num_clicked;
uint32_t num_short_clicked;
uint32_t num_single_clicked;
uint32_t num_double_clicked;
uint32_t num_triple_clicked;
uint32_t num_long_pressed;
};

static void click_event_cb(lv_event_t * e)
{
struct click_counts * counts = lv_event_get_user_data(e);
switch(lv_event_get_code(e)) {
case LV_EVENT_CLICKED:
counts->num_clicked++;
break;
case LV_EVENT_SHORT_CLICKED:
counts->num_short_clicked++;
break;
case LV_EVENT_SINGLE_CLICKED:
counts->num_single_clicked++;
break;
case LV_EVENT_DOUBLE_CLICKED:
counts->num_double_clicked++;
break;
case LV_EVENT_TRIPLE_CLICKED:
counts->num_triple_clicked++;
break;
case LV_EVENT_LONG_PRESSED:
counts->num_long_pressed++;
break;
default:
break;
}
}

void test_click(void)
{
/*Setup button that counts events.*/
struct click_counts counts;
lv_obj_t * btn = lv_button_create(lv_screen_active());
lv_obj_set_size(btn, 100, 100);
lv_obj_add_event_cb(btn, click_event_cb, LV_EVENT_CLICKED, &counts);
lv_obj_add_event_cb(btn, click_event_cb, LV_EVENT_SHORT_CLICKED, &counts);
lv_obj_add_event_cb(btn, click_event_cb, LV_EVENT_SINGLE_CLICKED, &counts);
lv_obj_add_event_cb(btn, click_event_cb, LV_EVENT_DOUBLE_CLICKED, &counts);
lv_obj_add_event_cb(btn, click_event_cb, LV_EVENT_TRIPLE_CLICKED, &counts);
lv_obj_add_event_cb(btn, click_event_cb, LV_EVENT_LONG_PRESSED, &counts);

/*Simple click.*/
lv_memzero(&counts, sizeof(counts));
lv_test_mouse_click_at(50, 50);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_short_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_single_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_double_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_triple_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_long_pressed);

/*Second click nearby.*/
lv_memzero(&counts, sizeof(counts));
lv_test_mouse_click_at(47, 52);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_short_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_single_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_double_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_triple_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_long_pressed);

/*Third click nearby.*/
lv_memzero(&counts, sizeof(counts));
lv_test_mouse_click_at(49, 55);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_short_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_single_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_double_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_triple_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_long_pressed);

/*Fourth click nearby.*/
lv_memzero(&counts, sizeof(counts));
lv_test_mouse_click_at(50, 50);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_short_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_single_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_double_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_triple_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_long_pressed);

/*Resetting the click streak due to distance.*/
lv_memzero(&counts, sizeof(counts));
lv_test_mouse_click_at(10, 10);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_short_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_single_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_double_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_triple_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_long_pressed);

/*Second click nearby.*/
lv_memzero(&counts, sizeof(counts));
lv_test_mouse_click_at(12, 14);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_short_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_single_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_double_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_triple_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_long_pressed);

/*Resetting the click streak due to time.*/
lv_memzero(&counts, sizeof(counts));
lv_test_indev_wait(1000);
lv_test_mouse_click_at(12, 14);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_short_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_single_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_double_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_triple_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_long_pressed);

/*Long press does not continue (or start) click streak.*/
lv_memzero(&counts, sizeof(counts));
lv_test_mouse_press();
lv_test_indev_wait(1000);
lv_test_mouse_release();
lv_test_indev_wait(50);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_short_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_single_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_double_clicked);
TEST_ASSERT_EQUAL_UINT32(0, counts.num_triple_clicked);
TEST_ASSERT_EQUAL_UINT32(1, counts.num_long_pressed);
}

#endif