diff --git a/src/core/lv_obj.c b/src/core/lv_obj.c index 682706ed38..14fd7d0b96 100644 --- a/src/core/lv_obj.c +++ b/src/core/lv_obj.c @@ -712,6 +712,12 @@ static void lv_obj_event(const lv_obj_class_t * class_p, lv_event_t * e) lv_obj_remove_state(obj, LV_STATE_PRESSED); lv_obj_remove_state(obj, LV_STATE_SCROLLED); } + else if(code == LV_EVENT_HOVER_OVER) { + lv_obj_add_state(obj, LV_STATE_HOVERED); + } + else if(code == LV_EVENT_HOVER_LEAVE) { + lv_obj_remove_state(obj, LV_STATE_HOVERED); + } } /** diff --git a/src/core/lv_obj_event.c b/src/core/lv_obj_event.c index 0553f3d777..1977466c34 100644 --- a/src/core/lv_obj_event.c +++ b/src/core/lv_obj_event.c @@ -193,7 +193,9 @@ lv_indev_t * lv_event_get_indev(lv_event_t * e) e->code == LV_EVENT_KEY || e->code == LV_EVENT_FOCUSED || e->code == LV_EVENT_DEFOCUSED || - e->code == LV_EVENT_LEAVE) { + e->code == LV_EVENT_LEAVE || + e->code == LV_EVENT_HOVER_OVER || + e->code == LV_EVENT_HOVER_LEAVE) { return lv_event_get_param(e); } else { diff --git a/src/display/lv_display_private.h b/src/display/lv_display_private.h index c7d2569cfd..155b9561c3 100644 --- a/src/display/lv_display_private.h +++ b/src/display/lv_display_private.h @@ -114,12 +114,12 @@ struct _lv_display_t { /** Screens of the display*/ lv_obj_t ** screens; /**< Array of screen objects.*/ + lv_obj_t * sys_layer; /**< @see lv_display_get_layer_sys*/ + lv_obj_t * top_layer; /**< @see lv_display_get_layer_top*/ lv_obj_t * act_scr; /**< Currently active screen on this display*/ + lv_obj_t * bottom_layer;/**< @see lv_display_get_layer_bottom*/ lv_obj_t * prev_scr; /**< Previous screen. Used during screen animations*/ lv_obj_t * scr_to_load; /**< The screen prepared to load in lv_screen_load_anim*/ - lv_obj_t * bottom_layer; /**< @see lv_display_get_layer_bottom*/ - lv_obj_t * top_layer; /**< @see lv_display_get_layer_top*/ - lv_obj_t * sys_layer; /**< @see lv_display_get_layer_sys*/ uint32_t screen_cnt; uint8_t draw_prev_over_act : 1;/** 1: Draw previous screen over active screen*/ uint8_t del_prev : 1; /** 1: Automatically delete the previous screen when the screen load animation is ready*/ diff --git a/src/indev/lv_indev.c b/src/indev/lv_indev.c index 055a633d64..08ff5be07a 100644 --- a/src/indev/lv_indev.c +++ b/src/indev/lv_indev.c @@ -679,7 +679,6 @@ static void indev_pointer_proc(lv_indev_t * i, lv_indev_data_t * data) i->pointer.last_point.x = i->pointer.act_point.x; i->pointer.last_point.y = i->pointer.act_point.y; - } /** @@ -1121,7 +1120,7 @@ static void indev_proc_press(lv_indev_t * indev) indev_obj_act = pointer_search_obj(disp, &indev->pointer.act_point); new_obj_searched = true; } - /*If there is an active object it's not scrolled and not protected also search*/ + /*If there is an active object it's not scrolled and not press locked also search*/ else if(indev->pointer.scroll_obj == NULL && lv_obj_has_flag(indev_obj_act, LV_OBJ_FLAG_PRESS_LOCK) == false) { indev_obj_act = pointer_search_obj(disp, &indev->pointer.act_point); @@ -1130,7 +1129,6 @@ static void indev_proc_press(lv_indev_t * indev) /*The scroll object might have scroll throw. Stop it manually*/ if(new_obj_searched && indev->pointer.scroll_obj) { - /*Attempt to stop scroll throw animation firstly*/ if(indev->scroll_throw_anim) { lv_anim_delete(indev, indev_scroll_throw_anim_cb); @@ -1146,6 +1144,17 @@ static void indev_proc_press(lv_indev_t * indev) indev->pointer.last_point.x = indev->pointer.act_point.x; indev->pointer.last_point.y = indev->pointer.act_point.y; + /*Without `LV_OBJ_FLAG_PRESS_LOCK` new widget can be found while pressing.*/ + if(indev->pointer.last_hovered && indev->pointer.last_hovered != indev_obj_act) { + lv_obj_send_event(indev->pointer.last_hovered, LV_EVENT_HOVER_LEAVE, indev); + if(indev_reset_check(indev)) return; + + lv_indev_send_event(indev, LV_EVENT_HOVER_LEAVE, indev->pointer.last_hovered); + if(indev_reset_check(indev)) return; + + indev->pointer.last_hovered = indev_obj_act; + } + /*If a new object found the previous was lost, so send a PRESS_LOST event*/ if(indev->pointer.act_obj != NULL) { /*Save the obj because in special cases `act_obj` can change in the event */ @@ -1181,6 +1190,9 @@ static void indev_proc_press(lv_indev_t * indev) const bool is_enabled = !lv_obj_has_state(indev_obj_act, LV_STATE_DISABLED); if(is_enabled) { + if(indev->pointer.last_hovered != indev_obj_act) { + if(send_event(LV_EVENT_HOVER_OVER, indev_act) == LV_RESULT_INVALID) return; + } if(send_event(LV_EVENT_PRESSED, indev_act) == LV_RESULT_INVALID) return; } @@ -1252,6 +1264,25 @@ static void indev_proc_press(lv_indev_t * indev) */ static void indev_proc_release(lv_indev_t * indev) { + if(indev->wait_until_release || /*Hover the new widget even if the coordinates didn't changed*/ + (indev->pointer.last_point.x != indev->pointer.act_point.x || + indev->pointer.last_point.y != indev->pointer.act_point.y)) { + lv_obj_t ** last = &indev->pointer.last_hovered; + lv_obj_t * hovered = pointer_search_obj(lv_display_get_default(), &indev->pointer.act_point); + if(*last != hovered) { + lv_obj_send_event(hovered, LV_EVENT_HOVER_OVER, indev); + if(indev_reset_check(indev)) return; + lv_indev_send_event(indev, LV_EVENT_HOVER_OVER, hovered); + if(indev_reset_check(indev)) return; + + lv_obj_send_event(*last, LV_EVENT_HOVER_LEAVE, indev); + if(indev_reset_check(indev)) return; + lv_indev_send_event(indev, LV_EVENT_HOVER_LEAVE, *last); + if(indev_reset_check(indev)) return; + *last = hovered; + } + } + if(indev->wait_until_release) { lv_obj_send_event(indev->pointer.act_obj, LV_EVENT_PRESS_LOST, indev_act); if(indev_reset_check(indev)) return; @@ -1390,7 +1421,8 @@ static void indev_proc_reset_query_handler(lv_indev_t * indev) if(indev->reset_query) { indev->pointer.act_obj = NULL; indev->pointer.last_obj = NULL; - indev->pointer.scroll_obj = NULL; + indev->pointer.scroll_obj = NULL; + indev->pointer.last_hovered = NULL; indev->long_pr_sent = 0; indev->pr_timestamp = 0; indev->longpr_rep_timestamp = 0; @@ -1582,6 +1614,9 @@ static void indev_reset_core(lv_indev_t * indev, lv_obj_t * obj) scroll_obj = NULL; } } + if(obj == NULL || indev->pointer.last_hovered == obj) { + indev->pointer.last_hovered = NULL; + } } } diff --git a/src/indev/lv_indev_private.h b/src/indev/lv_indev_private.h index 96ba4739aa..ddd7077a2c 100644 --- a/src/indev/lv_indev_private.h +++ b/src/indev/lv_indev_private.h @@ -85,6 +85,7 @@ struct _lv_indev_t { 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; diff --git a/src/misc/lv_event.h b/src/misc/lv_event.h index ec33d149cb..606ea04c07 100644 --- a/src/misc/lv_event.h +++ b/src/misc/lv_event.h @@ -61,6 +61,8 @@ typedef enum { LV_EVENT_LEAVE, /**< The object is defocused but still selected*/ LV_EVENT_HIT_TEST, /**< Perform advanced hit-testing*/ LV_EVENT_INDEV_RESET, /**< Indev has been reset*/ + LV_EVENT_HOVER_OVER, /**< Indev hover over object*/ + LV_EVENT_HOVER_LEAVE, /**< Indev hover leave object*/ /** Drawing events*/ LV_EVENT_COVER_CHECK, /**< Check if the object fully covers an area. The event parameter is `lv_cover_check_info_t *`.*/ diff --git a/tests/src/test_cases/test_hover.c b/tests/src/test_cases/test_hover.c new file mode 100644 index 0000000000..9396322b41 --- /dev/null +++ b/tests/src/test_cases/test_hover.c @@ -0,0 +1,95 @@ +#if LV_BUILD_TEST +#include "../lvgl.h" +#include "../lv_test_indev.h" +#include "unity/unity.h" + +#define TEST_HOVER_COUNTS 20 + +typedef struct _test_hover_t { + /* data */ + uint32_t id; + uint32_t counts; +} test_hover_t; + +static test_hover_t label_hovered; +static test_hover_t btn_hovered; + +static const lv_point_t pointer1[] = { + {0, 0}, {110, 20}, {150, 26}, {120, 19}, {0, 0}, +}; + +static const lv_point_t pointer2[] = { + {0, 0}, {60, 100}, {80, 100}, {120, 120}, {0, 0}, +}; + +void setUp(void) +{ + /* Function run before every test */ +} + +void tearDown(void) +{ + /* Function run after every test */ + lv_obj_clean(lv_screen_active()); +} + +static void hovered_event_cb(lv_event_t * e) +{ + test_hover_t * hover = (test_hover_t *)e->user_data; + lv_log("Object(ID:%d) hovered %d/%d times.\n", hover->id, hover->counts, TEST_HOVER_COUNTS); +} + +static void test_move_mouse(lv_point_t * point, uint8_t size) +{ + lv_point_t * p = point; + + for(uint8_t j = 0; j < TEST_HOVER_COUNTS; j++) { + for(uint8_t i = 0; i < size; i++) { + lv_test_mouse_move_to(p[i].x, p[i].y); + lv_test_indev_wait(50); + } + } +} + +void test_hover_basic(void) +{ + lv_obj_t * label = lv_label_create(lv_screen_active()); + lv_obj_set_size(label, 200, 20); + lv_label_set_text(label, "Clickable text can be hovered!"); + lv_obj_set_pos(label, 100, 20); + lv_obj_add_flag(label, LV_OBJ_FLAG_CLICKABLE); + lv_obj_set_style_text_color(label, lv_color_hex(0x5be1b6), LV_PART_MAIN | LV_STATE_HOVERED); + + /*Set hover callback*/ + label_hovered.id = (lv_uintptr_t)label->id; + label_hovered.counts = 0; + lv_obj_add_event_cb(label, hovered_event_cb, LV_EVENT_HOVER_OVER, &label_hovered); + + lv_obj_t * btn = lv_button_create(lv_screen_active()); + lv_obj_set_pos(btn, 64, 100); + lv_obj_set_size(btn, 128, 48); + lv_obj_set_style_bg_opa(btn, 128, LV_PART_MAIN | LV_STATE_HOVERED); + + /*Set hover callback*/ + btn_hovered.id = (lv_uintptr_t)btn->id; + btn_hovered.counts = 0; + lv_obj_add_event_cb(btn, hovered_event_cb, LV_EVENT_HOVER_OVER, &btn_hovered); + + test_move_mouse((lv_point_t *)pointer1, 5); + test_move_mouse((lv_point_t *)pointer2, 5); +} + +void test_hover_delete(void) +{ + lv_obj_t * button = lv_button_create(lv_screen_active()); + lv_obj_set_size(button, 200, 100); + + lv_test_indev_wait(50); + lv_test_mouse_move_to(50, 50); + lv_test_indev_wait(50); + lv_obj_delete(button); /*No crash while deleting the hovered button*/ + lv_test_indev_wait(50); +} + + +#endif