diff --git a/src/lv_core/lv_indev.c b/src/lv_core/lv_indev.c index 14579f071..8bf050d5f 100644 --- a/src/lv_core/lv_indev.c +++ b/src/lv_core/lv_indev.c @@ -1034,25 +1034,7 @@ lv_obj_t * lv_indev_search_obj(lv_obj_t * obj, lv_point_t *point) lv_obj_t * found_p = NULL; /*If the point is on this object check its children too*/ -#if LV_USE_EXT_CLICK_AREA == LV_EXT_CLICK_AREA_TINY - lv_area_t ext_area; - ext_area.x1 = obj->coords.x1 - obj->ext_click_pad_hor; - ext_area.x2 = obj->coords.x2 + obj->ext_click_pad_hor; - ext_area.y1 = obj->coords.y1 - obj->ext_click_pad_ver; - ext_area.y2 = obj->coords.y2 + obj->ext_click_pad_ver; - - if(lv_area_is_point_on(&ext_area, point)) { -#elif LV_USE_EXT_CLICK_AREA == LV_EXT_CLICK_AREA_FULL - lv_area_t ext_area; - ext_area.x1 = obj->coords.x1 - obj->ext_click_pad.x1; - ext_area.x2 = obj->coords.x2 + obj->ext_click_pad.x2; - ext_area.y1 = obj->coords.y1 - obj->ext_click_pad.y1; - ext_area.y2 = obj->coords.y2 + obj->ext_click_pad.y2; - - if(lv_area_is_point_on(&ext_area, point)) { -#else - if(lv_area_is_point_on(&obj->coords, point)) { -#endif + if(lv_obj_hittest(obj, point)) { lv_obj_t * i; LV_LL_READ(obj->child_ll, i) diff --git a/src/lv_core/lv_obj.c b/src/lv_core/lv_obj.c index 1d690e78f..781fc448f 100644 --- a/src/lv_core/lv_obj.c +++ b/src/lv_core/lv_obj.c @@ -201,6 +201,7 @@ lv_obj_t * lv_obj_create(lv_obj_t * parent, const lv_obj_t * copy) new_obj->group_p = NULL; #endif /*Set attributes*/ + new_obj->adv_hittest = 0; new_obj->click = 0; new_obj->drag = 0; new_obj->drag_throw = 0; @@ -1275,6 +1276,17 @@ void lv_obj_set_hidden(lv_obj_t * obj, bool en) par->signal_cb(par, LV_SIGNAL_CHILD_CHG, obj); } +/** + * Set whether advanced hit-testing is enabled on an object + * @param obj pointer to an object + * @param en true: advanced hit-testing is enabled + */ +void lv_obj_set_adv_hittest(lv_obj_t * obj, bool en) { + LV_ASSERT_OBJ(obj, LV_OBJX_NAME); + + obj->adv_hittest = en == false ? 0 : 1; +} + /** * Enable or disable the clicking of an object * @param obj pointer to an object @@ -2053,6 +2065,18 @@ bool lv_obj_get_hidden(const lv_obj_t * obj) return obj->hidden == 0 ? false : true; } +/** + * Get whether advanced hit-testing is enabled on an object + * @param obj pointer to an object + * @return true: advanced hit-testing is enabled + */ +bool lv_obj_get_adv_hittest(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, LV_OBJX_NAME); + + return obj->adv_hittest == 0 ? false : true; +} + /** * Get the click enable attribute of an object * @param obj pointer to an object @@ -2368,6 +2392,45 @@ bool lv_obj_is_focused(const lv_obj_t * obj) * OTHER FUNCTIONS *------------------*/ +/** + * Hit-test an object given a particular point in screen space. + * @param obj object to hit-test + * @param point screen-space point + * @return true if the object is considered under the point + */ +bool lv_obj_hittest(lv_obj_t * obj, lv_point_t * point) { +#if LV_USE_EXT_CLICK_AREA == LV_EXT_CLICK_AREA_TINY + lv_area_t ext_area; + ext_area.x1 = obj->coords.x1 - obj->ext_click_pad_hor; + ext_area.x2 = obj->coords.x2 + obj->ext_click_pad_hor; + ext_area.y1 = obj->coords.y1 - obj->ext_click_pad_ver; + ext_area.y2 = obj->coords.y2 + obj->ext_click_pad_ver; + + if(!lv_area_is_point_on(&ext_area, point, 0)) { +#elif LV_USE_EXT_CLICK_AREA == LV_EXT_CLICK_AREA_FULL + lv_area_t ext_area; + ext_area.x1 = obj->coords.x1 - obj->ext_click_pad.x1; + ext_area.x2 = obj->coords.x2 + obj->ext_click_pad.x2; + ext_area.y1 = obj->coords.y1 - obj->ext_click_pad.y1; + ext_area.y2 = obj->coords.y2 + obj->ext_click_pad.y2; + + if(!lv_area_is_point_on(&ext_area, point, 0)) { +#else + if(!lv_area_is_point_on(&obj->coords, point, 0)) { +#endif + return false; + } + if(obj->adv_hittest) { + lv_hit_test_info_t hit_info; + hit_info.point = point; + hit_info.result = true; + obj->signal_cb(obj, LV_SIGNAL_HIT_TEST, &hit_info); + if(!hit_info.result) + return false; + } + return true; +} + /** * Used in the signal callback to handle `LV_SIGNAL_GET_TYPE` signal * @param obj pointer to an object diff --git a/src/lv_core/lv_obj.h b/src/lv_core/lv_obj.h index c976cad6c..c22498bfd 100644 --- a/src/lv_core/lv_obj.h +++ b/src/lv_core/lv_obj.h @@ -128,6 +128,7 @@ enum { LV_SIGNAL_GET_TYPE, /**< LittlevGL needs to retrieve the object's type */ /*Input device related*/ + LV_SIGNAL_HIT_TEST, /**< Advanced hit-testing */ LV_SIGNAL_PRESSED, /**< The object has been pressed*/ LV_SIGNAL_PRESSING, /**< The object is being pressed (called continuously while pressing)*/ LV_SIGNAL_PRESS_LOST, /**< User is still pressing but slid cursor/finger off of the object */ @@ -225,7 +226,8 @@ typedef struct _lv_obj_t uint8_t parent_event : 1; /**< 1: Send the object's events to the parent too. */ lv_drag_dir_t drag_dir : 3; /**< Which directions the object can be dragged in */ lv_bidi_dir_t base_dir : 2; /**< Base direction of texts related to this object */ - uint8_t reserved : 3; /**< Reserved for future use*/ + uint8_t adv_hittest : 1; /**< 1: Use advanced hit-testing (slower) */ + uint8_t reserved : 2; /**< Reserved for future use*/ uint8_t protect; /**< Automatically happening actions can be prevented. 'OR'ed values from `lv_protect_t`*/ lv_opa_t opa_scale; /**< Scale down the opacity by this factor. Effects all children as well*/ @@ -263,6 +265,12 @@ typedef struct ... [x]: "lv_obj" */ } lv_obj_type_t; +typedef struct _lv_hit_test_info_t +{ + lv_point_t *point; + bool result; +} lv_hit_test_info_t; + /********************** * GLOBAL PROTOTYPES **********************/ @@ -466,6 +474,13 @@ void lv_obj_report_style_mod(lv_style_t * style); */ void lv_obj_set_hidden(lv_obj_t * obj, bool en); +/** + * Set whether advanced hit-testing is enabled on an object + * @param obj pointer to an object + * @param en true: advanced hit-testing is enabled + */ +void lv_obj_set_adv_hittest(lv_obj_t * obj, bool en); + /** * Enable or disable the clicking of an object * @param obj pointer to an object @@ -808,6 +823,13 @@ const lv_style_t * lv_obj_get_style(const lv_obj_t * obj); */ bool lv_obj_get_hidden(const lv_obj_t * obj); +/** + * Get whether advanced hit-testing is enabled on an object + * @param obj pointer to an object + * @return true: advanced hit-testing is enabled + */ +bool lv_obj_get_adv_hittest(const lv_obj_t * obj); + /** * Get the click enable attribute of an object * @param obj pointer to an object @@ -914,6 +936,8 @@ lv_event_cb_t lv_obj_get_event_cb(const lv_obj_t * obj); * Other get *-----------------*/ +bool lv_obj_hittest(lv_obj_t * obj, lv_point_t * point); + /** * Get the ext pointer * @param obj pointer to an object diff --git a/src/lv_misc/lv_area.c b/src/lv_misc/lv_area.c index de649c5b0..174e4f860 100644 --- a/src/lv_misc/lv_area.c +++ b/src/lv_misc/lv_area.c @@ -27,6 +27,8 @@ * STATIC PROTOTYPES **********************/ +static bool lv_point_within_circle(const lv_area_t * area, const lv_point_t * p); + /********************** * STATIC VARIABLES **********************/ @@ -148,15 +150,62 @@ void lv_area_join(lv_area_t * a_res_p, const lv_area_t * a1_p, const lv_area_t * * @param p_p pointer to a point * @return false:the point is out of the area */ -bool lv_area_is_point_on(const lv_area_t * a_p, const lv_point_t * p_p) +bool lv_area_is_point_on(const lv_area_t * a_p, const lv_point_t * p_p, lv_coord_t radius) { - bool is_on = false; - + /*First check the basic area*/ + bool is_on_rect = false; if((p_p->x >= a_p->x1 && p_p->x <= a_p->x2) && ((p_p->y >= a_p->y1 && p_p->y <= a_p->y2))) { - is_on = true; + is_on_rect = true; } - - return is_on; + if(!is_on_rect) + return false; + /*Now handle potential rounded rectangles*/ + if(radius <= 0) { + /*No radius, it is within the rectangle*/ + return true; + } + lv_coord_t max_radius = LV_MATH_MIN(lv_area_get_width(a_p) / 2, lv_area_get_height(a_p) / 2); + if(radius > max_radius) + radius = max_radius; + + /*Check if it's in one of the corners*/ + lv_area_t corner_area; + /*Top left*/ + corner_area.x1 = a_p->x1; + corner_area.x2 = a_p->x1 + radius; + corner_area.y1 = a_p->y1; + corner_area.y2 = a_p->y1 + radius; + if(lv_area_is_point_on(&corner_area, p_p, 0)) { + corner_area.x2 += radius; + corner_area.y2 += radius; + return lv_point_within_circle(&corner_area, p_p); + } + /*Bottom left*/ + corner_area.y1 = a_p->y2 - radius; + corner_area.y2 = a_p->y2; + if(lv_area_is_point_on(&corner_area, p_p, 0)) { + corner_area.x2 += radius; + corner_area.y1 -= radius; + return lv_point_within_circle(&corner_area, p_p); + } + /*Bottom right*/ + corner_area.x1 = a_p->x2 - radius; + corner_area.x2 = a_p->x2; + if(lv_area_is_point_on(&corner_area, p_p, 0)) { + corner_area.x1 -= radius; + corner_area.y1 -= radius; + return lv_point_within_circle(&corner_area, p_p); + } + /*Top right*/ + corner_area.y1 = a_p->y1; + corner_area.y2 = a_p->y1 + radius; + if(lv_area_is_point_on(&corner_area, p_p, 0)) { + corner_area.x1 -= radius; + corner_area.y2 += radius; + return lv_point_within_circle(&corner_area, p_p); + } + /*Not within corners*/ + return false; } /** @@ -208,3 +257,24 @@ void lv_area_increment(lv_area_t * a_p, const lv_coord_t amount) /********************** * STATIC FUNCTIONS **********************/ + +static bool lv_point_within_circle(const lv_area_t * area, const lv_point_t * p) +{ + lv_coord_t r = (area->x2 - area->x1) / 2; + + /* Circle center */ + lv_coord_t cx = area->x1 + r; + lv_coord_t cy = area->y1 + r; + + /*Simplify the code by moving everything to (0, 0) */ + lv_coord_t px = p->x - cx; + lv_coord_t py = p->y - cy; + + int32_t r_sqrd = r*r; + int32_t dist = (px*px) + (py*py); + + if(dist <= r_sqrd) + return true; + else + return false; +} diff --git a/src/lv_misc/lv_area.h b/src/lv_misc/lv_area.h index 211bebd84..375fa009f 100644 --- a/src/lv_misc/lv_area.h +++ b/src/lv_misc/lv_area.h @@ -148,9 +148,10 @@ void lv_area_join(lv_area_t * a_res_p, const lv_area_t * a1_p, const lv_area_t * * Check if a point is on an area * @param a_p pointer to an area * @param p_p pointer to a point + * @param radius radius of area (e.g. for rounded rectangle) * @return false:the point is out of the area */ -bool lv_area_is_point_on(const lv_area_t * a_p, const lv_point_t * p_p); +bool lv_area_is_point_on(const lv_area_t * a_p, const lv_point_t * p_p, lv_coord_t radius); /** * Check if two area has common parts diff --git a/src/lv_objx/lv_btnm.c b/src/lv_objx/lv_btnm.c index 9b38a70f3..e783a1045 100644 --- a/src/lv_objx/lv_btnm.c +++ b/src/lv_objx/lv_btnm.c @@ -1056,7 +1056,7 @@ static uint16_t get_button_from_point(lv_obj_t * btnm, lv_point_t * p) btn_area.y1 += btnm_cords.y1; btn_area.x2 += btnm_cords.x1; btn_area.y2 += btnm_cords.y1; - if(lv_area_is_point_on(&btn_area, p) != false) { + if(lv_area_is_point_on(&btn_area, p, 0) != false) { break; } } diff --git a/src/lv_objx/lv_calendar.c b/src/lv_objx/lv_calendar.c index 1dd83464f..1249340f1 100644 --- a/src/lv_objx/lv_calendar.c +++ b/src/lv_objx/lv_calendar.c @@ -508,7 +508,7 @@ static lv_res_t lv_calendar_signal(lv_obj_t * calendar, lv_signal_t sign, void * lv_indev_get_point(indev, &p); /*If the header is pressed mark an arrow as pressed*/ - if(lv_area_is_point_on(&header_area, &p)) { + if(lv_area_is_point_on(&header_area, &p, 0)) { if(p.x < header_area.x1 + lv_area_get_width(&header_area) / 2) { if(ext->btn_pressing != -1) lv_obj_invalidate(calendar); ext->btn_pressing = -1; @@ -605,7 +605,7 @@ static bool calculate_touched_day(lv_obj_t * calendar, const lv_point_t * touche days_area.y1 = calendar->coords.y1 + get_header_height(calendar) + get_day_names_height(calendar) - style_bg->body.padding.top; - if(lv_area_is_point_on(&days_area, touched_point)) { + if(lv_area_is_point_on(&days_area, touched_point, 0)) { lv_coord_t w = (days_area.x2 - days_area.x1 + 1) / 7; lv_coord_t h = (days_area.y2 - days_area.y1 + 1) / 6; uint8_t x_pos = 0; diff --git a/src/lv_objx/lv_cpicker.c b/src/lv_objx/lv_cpicker.c index aca670083..4f6be702c 100644 --- a/src/lv_objx/lv_cpicker.c +++ b/src/lv_objx/lv_cpicker.c @@ -57,6 +57,7 @@ **********************/ static lv_design_res_t lv_cpicker_design(lv_obj_t * cpicker, const lv_area_t * clip_area, lv_design_mode_t mode); static lv_res_t lv_cpicker_signal(lv_obj_t * cpicker, lv_signal_t sign, void * param); +static bool lv_cpicker_hit(lv_obj_t * cpicker, const lv_point_t * p); static void draw_rect_grad(lv_obj_t * cpicker, const lv_area_t * mask, lv_opa_t opa_scale); static void draw_disc_grad(lv_obj_t * cpicker, const lv_area_t * mask, lv_opa_t opa_scale); @@ -889,11 +890,33 @@ static lv_res_t lv_cpicker_signal(lv_obj_t * cpicker, lv_signal_t sign, void * p res = lv_event_send(cpicker, LV_EVENT_VALUE_CHANGED, NULL); if(res != LV_RES_OK) return res; } + } else if(sign == LV_SIGNAL_HIT_TEST) { + lv_hit_test_info_t *info = param; + info->result = lv_cpicker_hit(cpicker, info->point); } return res; } +static bool lv_cpicker_hit(lv_obj_t * cpicker, const lv_point_t * p) +{ + lv_cpicker_ext_t * ext = (lv_cpicker_ext_t *)lv_obj_get_ext_attr(cpicker); + if(ext->type != LV_CPICKER_TYPE_DISC || ext->preview) + return true; + const lv_style_t * style_main = lv_cpicker_get_style(cpicker, LV_CPICKER_STYLE_MAIN); + lv_area_t area_mid; + lv_area_copy(&area_mid, &cpicker->coords); + area_mid.x1 += style_main->line.width; + area_mid.y1 += style_main->line.width; + area_mid.x2 -= style_main->line.width; + area_mid.y2 -= style_main->line.width; + + if(lv_area_is_point_on(&area_mid, p, LV_RADIUS_CIRCLE)) + return false; + + return true; +} + static void next_color_mode(lv_obj_t * cpicker) { lv_cpicker_ext_t * ext = lv_obj_get_ext_attr(cpicker);