diff --git a/docs/widgets/arc.rst b/docs/widgets/arc.rst index a9c923bb5e..ffadf01062 100644 --- a/docs/widgets/arc.rst +++ b/docs/widgets/arc.rst @@ -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 -------------------------------- diff --git a/src/widgets/arc/lv_arc.c b/src/widgets/arc/lv_arc.c index 7e4315c29f..6aa14840d3 100644 --- a/src/widgets/arc/lv_arc.c +++ b/src/widgets/arc/lv_arc.c @@ -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) { diff --git a/tests/ref_imgs/widgets/overlapping_arcs_test.png b/tests/ref_imgs/widgets/overlapping_arcs_test.png new file mode 100644 index 0000000000..878585780d Binary files /dev/null and b/tests/ref_imgs/widgets/overlapping_arcs_test.png differ diff --git a/tests/ref_imgs_vg_lite/widgets/overlapping_arcs_test.png b/tests/ref_imgs_vg_lite/widgets/overlapping_arcs_test.png new file mode 100644 index 0000000000..25e6dba957 Binary files /dev/null and b/tests/ref_imgs_vg_lite/widgets/overlapping_arcs_test.png differ diff --git a/tests/src/test_cases/widgets/test_arc.c b/tests/src/test_cases/widgets/test_arc.c index 0d6bd6ad6f..81da1920d6 100644 --- a/tests/src/test_cases/widgets/test_arc.c +++ b/tests/src/test_cases/widgets/test_arc.c @@ -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