fix(arc): ignore hits that are outside drawn background arc (#6753)

This commit is contained in:
Luca Vanesche
2024-09-03 11:55:55 +02:00
committed by Gabor Kiss-Vamosi
parent c11fda693c
commit 3a96eaa08a
5 changed files with 105 additions and 9 deletions

View File

@@ -105,13 +105,19 @@ the object non-clickable:
lv_obj_remove_style(arc, NULL, LV_PART_KNOB);
lv_obj_remove_flag(arc, LV_OBJ_FLAG_CLICKABLE);
Advanced hit test
-----------------
Interactive area
----------------
If the :cpp:enumerator:`LV_OBJ_FLAG_ADV_HITTEST` flag is enabled the arc can be
clicked through in the middle. Clicks are recognized only on the ring of
the background arc. :cpp:func:`lv_obj_set_ext_click_size` makes the sensitive
area larger inside and outside with the given number of pixels.
By default :cpp:enumerator:`LV_OBJ_FLAG_ADV_HITTEST` is disabled which
means the arc's whole area is interactive.
As usual :cpp:func:`lv_obj_set_ext_click_size` can be used to increase
the sensitive area outside the arc by a specified number of pixels.
If :cpp:enumerator:`LV_OBJ_FLAG_ADV_HITTEST` is enabled the arc will be sensitive only
in the range of start and end background angles and on the arc itself (not inside the arc).
In this case ``ext_click_size`` makes the sensitive area ticker both inward and outward.
Additionally, a tolerance of :cpp:expr:`lv_dpx(50)` pixels is applied to each angle, extending the
hit-test range along the arc's length.
Place another object to the knob
--------------------------------

View File

@@ -199,6 +199,9 @@ void lv_arc_set_rotation(lv_obj_t * obj, int32_t rotation)
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_arc_t * arc = (lv_arc_t *)obj;
/* ensure the angle is in the range [0, 360) */
while(rotation < 0) rotation += 360;
while(rotation >= 360) rotation -= 360;
arc->rotation = rotation;
lv_obj_invalidate(obj);
@@ -506,8 +509,9 @@ static void lv_arc_event(const lv_obj_class_t * class_p, lv_event_t * e)
angle -= arc->rotation;
angle -= arc->bg_angle_start; /*Make the angle relative to the start angle*/
/* If we click near the bg_angle_start the angle will be close to 360° instead of a small angle */
if(angle < 0) angle += 360;
/* ensure the angle is in the range [0, 360) */
while(angle < 0) angle += 360;
while(angle >= 360) angle -= 360;
const uint32_t circumference = (uint32_t)((2U * r * 314U) / 100U); /* Equivalent to: 2r * 3.14, avoiding floats */
const lv_value_precise_t tolerance_deg = (360 * lv_dpx(50U)) / circumference;
@@ -655,6 +659,25 @@ static void lv_arc_event(const lv_obj_class_t * class_p, lv_event_t * e)
return;
}
/*Calculate the angle of the pressed point*/
lv_value_precise_t angle = lv_atan2(info->point->y - p.y, info->point->x - p.x);
angle -= arc->rotation;
angle -= arc->bg_angle_start; /*Make the angle relative to the start angle*/
/* ensure the angle is in the range [0, 360) */
while(angle < 0) angle += 360;
while(angle >= 360) angle -= 360;
const uint32_t circumference = (uint32_t)((2U * r * 314U) / 100U); /* Equivalent to: 2r * 3.14, avoiding floats */
const lv_value_precise_t tolerance_deg = (360 * lv_dpx(50U)) / circumference;
/* Check if the angle is outside the drawn background arc */
const bool is_angle_within_bg_bounds = lv_arc_angle_within_bg_bounds(obj, angle, tolerance_deg);
if(!is_angle_within_bg_bounds) {
info->res = false;
return;
}
/*Valid if no clicked outside*/
lv_area_increase(&a, w + ext_click_area * 2, w + ext_click_area * 2);
info->res = lv_area_is_point_on(&a, info->point, LV_RADIUS_CIRCLE);
@@ -941,7 +964,10 @@ static bool lv_arc_angle_within_bg_bounds(lv_obj_t * obj, const lv_value_precise
lv_arc_t * arc = (lv_arc_t *)obj;
lv_value_precise_t bounds_angle = arc->bg_angle_end - arc->bg_angle_start;
if(bounds_angle < 0) bounds_angle += 360;
/* ensure the angle is in the range [0, 360) */
while(bounds_angle < 0) bounds_angle += 360;
while(bounds_angle >= 360) bounds_angle -= 360;
/* Angle is in the bounds */
if(angle <= bounds_angle) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -18,9 +18,12 @@ void test_arc_angles_when_reversed(void);
static lv_obj_t * active_screen = NULL;
static lv_obj_t * arc = NULL;
static lv_obj_t * arc2 = NULL;
static uint32_t event_cnt;
static uint32_t event_cnt2;
static void dummy_event_cb(lv_event_t * e);
static void dummy_event_cb2(lv_event_t * e);
void setUp(void)
{
@@ -232,10 +235,71 @@ void test_arc_click_sustained_from_start_to_end_does_not_set_value_to_max(void)
TEST_ASSERT_EQUAL_SCREENSHOT("widgets/arc_3.png");
}
void test_two_overlapping_arcs_can_be_interacted_independently(void)
{
arc = lv_arc_create(lv_screen_active());
arc2 = lv_arc_create(lv_screen_active());
lv_arc_set_value(arc, 0);
lv_arc_set_value(arc2, 0);
lv_obj_set_size(arc, 100, 100);
lv_obj_set_size(arc2, 100, 100);
lv_arc_set_bg_angles(arc, 20, 160);
lv_arc_set_bg_angles(arc2, 200, 340);
lv_obj_add_flag(arc, LV_OBJ_FLAG_ADV_HITTEST);
lv_obj_add_flag(arc2, LV_OBJ_FLAG_ADV_HITTEST);
lv_arc_set_value(arc, 10);
lv_arc_set_value(arc2, 10);
lv_arc_set_rotation(arc, 355);
lv_arc_set_rotation(arc2, 355);
lv_obj_center(arc);
lv_obj_center(arc2);
// Add event callback to both arcs
lv_obj_add_event_cb(arc, dummy_event_cb, LV_EVENT_PRESSED, NULL);
lv_obj_add_event_cb(arc2, dummy_event_cb2, LV_EVENT_PRESSED, NULL);
// Reset event counters
event_cnt = 0;
event_cnt2 = 0;
// Click on the position of the first arc (center)
lv_test_mouse_move_to(400, 195);
lv_test_mouse_press();
lv_test_indev_wait(500);
lv_test_mouse_release();
lv_test_indev_wait(50);
// Verify that the event callback was called for the first arc
TEST_ASSERT_EQUAL_UINT32(0, event_cnt);
TEST_ASSERT_EQUAL_UINT32(1, event_cnt2);
// click on the position of the second arc (center)
lv_test_mouse_move_to(400, 285);
lv_test_mouse_press();
lv_test_indev_wait(500);
lv_test_mouse_release();
lv_test_indev_wait(50);
// Verify that the event callback was called for the second arc
TEST_ASSERT_EQUAL_UINT32(1, event_cnt);
TEST_ASSERT_EQUAL_UINT32(1, event_cnt2);
// Verify that the screen remains as expected after the interactions
TEST_ASSERT_EQUAL_SCREENSHOT("widgets/overlapping_arcs_test.png");
}
static void dummy_event_cb(lv_event_t * e)
{
LV_UNUSED(e);
event_cnt++;
}
static void dummy_event_cb2(lv_event_t * e)
{
LV_UNUSED(e);
event_cnt2++;
}
#endif