diff --git a/docs/overview/event.rst b/docs/overview/event.rst index 1a6607ea9..7a85906b6 100644 --- a/docs/overview/event.rst +++ b/docs/overview/event.rst @@ -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`: diff --git a/examples/event/index.rst b/examples/event/index.rst index 0e4dea7f1..fb6a7b03c 100644 --- a/examples/event/index.rst +++ b/examples/event/index.rst @@ -2,23 +2,29 @@ Button click event ------------------ -.. lv_example:: event/lv_example_event_1 +.. lv_example:: event/lv_example_event_click + :language: c + +Click streaks +------------- + +.. lv_example:: event/lv_example_event_streak :language: c Handle multiple events ---------------------- -.. lv_example:: event/lv_example_event_2 +.. lv_example:: event/lv_example_event_button :language: c Event bubbling -------------- -.. lv_example:: event/lv_example_event_3 +.. lv_example:: event/lv_example_event_bubble :language: c Draw event ---------- -.. lv_example:: event/lv_example_event_4 +.. lv_example:: event/lv_example_event_draw :language: c diff --git a/examples/event/lv_example_event.h b/examples/event/lv_example_event.h index 591f4f947..44588a704 100644 --- a/examples/event/lv_example_event.h +++ b/examples/event/lv_example_event.h @@ -25,10 +25,11 @@ extern "C" { /********************** * GLOBAL PROTOTYPES **********************/ -void lv_example_event_1(void); -void lv_example_event_2(void); -void lv_example_event_3(void); -void lv_example_event_4(void); +void lv_example_event_click(void); +void lv_example_event_streak(void); +void lv_example_event_button(void); +void lv_example_event_bubble(void); +void lv_example_event_draw(void); /********************** * MACROS diff --git a/examples/event/lv_example_event_3.c b/examples/event/lv_example_event_bubble.c similarity index 97% rename from examples/event/lv_example_event_3.c rename to examples/event/lv_example_event_bubble.c index 6a8f3cb21..dedac2f38 100644 --- a/examples/event/lv_example_event_3.c +++ b/examples/event/lv_example_event_bubble.c @@ -19,7 +19,7 @@ static void event_cb(lv_event_t * e) /** * Demonstrate event bubbling */ -void lv_example_event_3(void) +void lv_example_event_bubble(void) { lv_obj_t * cont = lv_obj_create(lv_screen_active()); diff --git a/examples/event/lv_example_event_2.c b/examples/event/lv_example_event_button.c similarity index 97% rename from examples/event/lv_example_event_2.c rename to examples/event/lv_example_event_button.c index 35d2f757c..8f5ea30e0 100644 --- a/examples/event/lv_example_event_2.c +++ b/examples/event/lv_example_event_button.c @@ -27,7 +27,7 @@ static void event_cb(lv_event_t * e) /** * Handle multiple events */ -void lv_example_event_2(void) +void lv_example_event_button(void) { lv_obj_t * btn = lv_button_create(lv_screen_active()); lv_obj_set_size(btn, 100, 50); diff --git a/examples/event/lv_example_event_1.c b/examples/event/lv_example_event_click.c similarity index 95% rename from examples/event/lv_example_event_1.c rename to examples/event/lv_example_event_click.c index a6162859f..fd8100266 100644 --- a/examples/event/lv_example_event_1.c +++ b/examples/event/lv_example_event_click.c @@ -15,7 +15,7 @@ static void event_cb(lv_event_t * e) /** * Add click event to a button */ -void lv_example_event_1(void) +void lv_example_event_click(void) { lv_obj_t * btn = lv_button_create(lv_screen_active()); lv_obj_set_size(btn, 100, 50); diff --git a/examples/event/lv_example_event_4.c b/examples/event/lv_example_event_draw.c similarity index 98% rename from examples/event/lv_example_event_4.c rename to examples/event/lv_example_event_draw.c index 1d38587d7..721e9c75a 100644 --- a/examples/event/lv_example_event_4.c +++ b/examples/event/lv_example_event_draw.c @@ -47,7 +47,7 @@ static void event_cb(lv_event_t * e) /** * Demonstrate the usage of draw event */ -void lv_example_event_4(void) +void lv_example_event_draw(void) { lv_obj_t * cont = lv_obj_create(lv_screen_active()); lv_obj_set_size(cont, 200, 200); diff --git a/examples/event/lv_example_event_streak.c b/examples/event/lv_example_event_streak.c new file mode 100644 index 000000000..de1a89fbc --- /dev/null +++ b/examples/event/lv_example_event_streak.c @@ -0,0 +1,40 @@ +#include "../lv_examples.h" +#if LV_BUILD_EXAMPLES && LV_USE_LABEL + +static void short_click_event_cb(lv_event_t * e) +{ + LV_LOG_USER("Short clicked"); + + lv_obj_t * info_label = lv_event_get_user_data(e); + lv_indev_t * indev = lv_event_get_param(e); + uint8_t cnt = lv_indev_get_short_click_streak(indev); + lv_label_set_text_fmt(info_label, "Short click streak: %"LV_PRIu32, cnt); +} + +static void streak_event_cb(lv_event_t * e) +{ + lv_obj_t * btn = lv_event_get_target(e); + lv_obj_t * label = lv_obj_get_child(btn, 0); + const char * text = lv_event_get_user_data(e); + lv_label_set_text(label, text); +} + +void lv_example_event_streak(void) +{ + lv_obj_t * info_label = lv_label_create(lv_screen_active()); + lv_label_set_text(info_label, "No events yet"); + + lv_obj_t * btn = lv_button_create(lv_screen_active()); + lv_obj_set_size(btn, 100, 50); + lv_obj_center(btn); + lv_obj_add_event_cb(btn, short_click_event_cb, LV_EVENT_SHORT_CLICKED, info_label); + lv_obj_add_event_cb(btn, streak_event_cb, LV_EVENT_SINGLE_CLICKED, "Single clicked"); + lv_obj_add_event_cb(btn, streak_event_cb, LV_EVENT_DOUBLE_CLICKED, "Double clicked"); + lv_obj_add_event_cb(btn, streak_event_cb, LV_EVENT_TRIPLE_CLICKED, "Triple clicked"); + + lv_obj_t * label = lv_label_create(btn); + lv_label_set_text(label, "Click me!"); + lv_obj_center(label); +} + +#endif diff --git a/src/indev/lv_indev.c b/src/indev/lv_indev.c index 3a4a5721a..40d6050f9 100644 --- a/src/indev/lv_indev.c +++ b/src/indev/lv_indev.c @@ -71,6 +71,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); @@ -485,6 +486,11 @@ uint32_t lv_indev_get_key(const lv_indev_t * indev) return key; } +uint8_t lv_indev_get_short_click_streak(const lv_indev_t * indev) +{ + return indev->pointer.short_click_streak; +} + lv_dir_t lv_indev_get_scroll_dir(const lv_indev_t * indev) { if(indev == NULL) return false; @@ -852,7 +858,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; @@ -864,6 +870,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 @@ -1015,7 +1023,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) { @@ -1029,7 +1037,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; } @@ -1353,7 +1361,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; } @@ -1401,6 +1409,40 @@ static void indev_proc_release(lv_indev_t * indev) } } +static lv_result_t indev_proc_short_click(lv_indev_t * indev) +{ + /*Update streak for clicks within small distance and short time*/ + indev->pointer.short_click_streak++; + if(lv_tick_elaps(indev->pointer.last_short_click_timestamp) > indev->long_press_time) { + indev->pointer.short_click_streak = 1; + } + else if(indev->type == LV_INDEV_TYPE_POINTER || indev->type == LV_INDEV_TYPE_BUTTON) { + 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) indev->pointer.short_click_streak = 1; + } + + indev->pointer.last_short_click_timestamp = lv_tick_get(); + lv_indev_get_point(indev, &indev->pointer.last_short_click_point); + + /*Simple short click*/ + lv_result_t res = send_event(LV_EVENT_SHORT_CLICKED, indev_act); + if(res == LV_RESULT_INVALID) { + return res; + } + + /*Cycle through single/double/triple click*/ + switch((indev->pointer.short_click_streak - 1) % 3) { + case 0: + return send_event(LV_EVENT_SINGLE_CLICKED, indev_act); + case 1: + return send_event(LV_EVENT_DOUBLE_CLICKED, indev_act); + case 2: + return 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; diff --git a/src/indev/lv_indev.h b/src/indev/lv_indev.h index 81f142474..bee5ec1a9 100644 --- a/src/indev/lv_indev.h +++ b/src/indev/lv_indev.h @@ -285,6 +285,15 @@ lv_dir_t lv_indev_get_gesture_dir(const lv_indev_t * indev); */ 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) diff --git a/src/indev/lv_indev_private.h b/src/indev/lv_indev_private.h index 85883c3ce..9bf980914 100644 --- a/src/indev/lv_indev_private.h +++ b/src/indev/lv_indev_private.h @@ -90,7 +90,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; + lv_point_t last_short_click_point; + uint32_t last_short_click_timestamp; /*Flags*/ uint8_t scroll_dir : 4; uint8_t gesture_dir : 4; diff --git a/src/misc/lv_event.h b/src/misc/lv_event.h index c6610209b..89aee8abd 100644 --- a/src/misc/lv_event.h +++ b/src/misc/lv_event.h @@ -39,6 +39,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)*/ diff --git a/tests/src/test_cases/test_click.c b/tests/src/test_cases/test_click.c new file mode 100644 index 000000000..9cff59774 --- /dev/null +++ b/tests/src/test_cases/test_click.c @@ -0,0 +1,164 @@ +#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; + uint8_t short_click_streak; +}; + +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; + } + + lv_indev_t * indev = lv_event_get_param(e); + counts->short_click_streak = lv_indev_get_short_click_streak(indev); +} + +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); + TEST_ASSERT_EQUAL_UINT8(1, counts.short_click_streak); + + /*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); + TEST_ASSERT_EQUAL_UINT8(2, counts.short_click_streak); + + /*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); + TEST_ASSERT_EQUAL_UINT8(3, counts.short_click_streak); + + /*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); + TEST_ASSERT_EQUAL_UINT8(4, counts.short_click_streak); + + /*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); + TEST_ASSERT_EQUAL_UINT8(1, counts.short_click_streak); + + /*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); + TEST_ASSERT_EQUAL_UINT8(2, counts.short_click_streak); + + /*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); + TEST_ASSERT_EQUAL_UINT8(1, counts.short_click_streak); + + /*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); + TEST_ASSERT_EQUAL_UINT8(1, counts.short_click_streak); +} + +#endif