diff --git a/examples/widgets/lv_example_widgets.h b/examples/widgets/lv_example_widgets.h index b1a35a8f9..11846b809 100644 --- a/examples/widgets/lv_example_widgets.h +++ b/examples/widgets/lv_example_widgets.h @@ -116,6 +116,7 @@ void lv_example_scale_2(void); void lv_example_scale_3(void); void lv_example_scale_4(void); void lv_example_scale_5(void); +void lv_example_scale_6(void); void lv_example_slider_1(void); void lv_example_slider_2(void); diff --git a/examples/widgets/scale/index.rst b/examples/widgets/scale/index.rst index 1fd123d5f..e9bfa5a5a 100644 --- a/examples/widgets/scale/index.rst +++ b/examples/widgets/scale/index.rst @@ -28,3 +28,9 @@ An scale with section and custom styling .. lv_example:: widgets/scale/lv_example_scale_5 :language: c +A round scale with multiple needles, resembling a clock +""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +.. lv_example:: widgets/scale/lv_example_scale_6 + :language: c + diff --git a/examples/widgets/scale/lv_example_scale_6.c b/examples/widgets/scale/lv_example_scale_6.c new file mode 100644 index 000000000..478e4a0a9 --- /dev/null +++ b/examples/widgets/scale/lv_example_scale_6.c @@ -0,0 +1,126 @@ +#include "../../lv_examples.h" +#if LV_USE_SCALE && LV_BUILD_EXAMPLES + +#if LV_USE_FLOAT + #define my_PRIprecise "f" +#else + #define my_PRIprecise LV_PRId32 +#endif + +static lv_obj_t * scale; +static lv_obj_t * minute_hand; +static lv_obj_t * hour_hand; +static lv_point_precise_t minute_hand_points[2]; +static int32_t hour; +static int32_t minute; + +static void timer_cb(lv_timer_t * timer) +{ + LV_UNUSED(timer); + + minute++; + if(minute > 59) { + minute = 0; + hour++; + if(hour > 11) { + hour = 0; + } + } + + /** + * the scale will store the needle line points in the existing + * point array if one was set with `lv_line_set_points_mutable`. + * Otherwise, it will allocate the needle line points. + */ + + /* the scale will store the minute hand line points in `minute_hand_points` */ + lv_scale_set_line_needle_value(scale, minute_hand, 60, minute); + /* log the points that were stored in the array */ + LV_LOG_USER( + "minute hand points - " + "0: (%" my_PRIprecise ", %" my_PRIprecise "), " + "1: (%" my_PRIprecise ", %" my_PRIprecise ")", + minute_hand_points[0].x, minute_hand_points[0].y, + minute_hand_points[1].x, minute_hand_points[1].y + ); + + /* the scale will allocate the hour hand line points */ + lv_scale_set_line_needle_value(scale, hour_hand, 40, hour * 5 + (minute / 12)); +} + +/** + * A round scale with multiple needles, resembing a clock + */ +void lv_example_scale_6(void) +{ + scale = lv_scale_create(lv_screen_active()); + + lv_obj_set_size(scale, 150, 150); + lv_scale_set_mode(scale, LV_SCALE_MODE_ROUND_INNER); + lv_obj_set_style_bg_opa(scale, LV_OPA_60, 0); + lv_obj_set_style_bg_color(scale, lv_color_black(), 0); + lv_obj_set_style_radius(scale, LV_RADIUS_CIRCLE, 0); + lv_obj_set_style_clip_corner(scale, true, 0); + lv_obj_center(scale); + + lv_scale_set_label_show(scale, true); + + lv_scale_set_total_tick_count(scale, 61); + lv_scale_set_major_tick_every(scale, 5); + + static const char * hour_ticks[] = {"12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", NULL}; + lv_scale_set_text_src(scale, hour_ticks); + + static lv_style_t indicator_style; + lv_style_init(&indicator_style); + + /* Label style properties */ + lv_style_set_text_font(&indicator_style, LV_FONT_DEFAULT); + lv_style_set_text_color(&indicator_style, lv_palette_main(LV_PALETTE_YELLOW)); + + /* Major tick properties */ + lv_style_set_line_color(&indicator_style, lv_palette_main(LV_PALETTE_YELLOW)); + lv_style_set_length(&indicator_style, 8); /* tick length */ + lv_style_set_line_width(&indicator_style, 2); /* tick width */ + lv_obj_add_style(scale, &indicator_style, LV_PART_INDICATOR); + + /* Minor tick properties */ + static lv_style_t minor_ticks_style; + lv_style_init(&minor_ticks_style); + lv_style_set_line_color(&minor_ticks_style, lv_palette_main(LV_PALETTE_YELLOW)); + lv_style_set_length(&minor_ticks_style, 6); /* tick length */ + lv_style_set_line_width(&minor_ticks_style, 2); /* tick width */ + lv_obj_add_style(scale, &minor_ticks_style, LV_PART_ITEMS); + + /* Main line properties */ + static lv_style_t main_line_style; + lv_style_init(&main_line_style); + lv_style_set_arc_color(&main_line_style, lv_color_black()); + lv_style_set_arc_width(&main_line_style, 5); + lv_obj_add_style(scale, &main_line_style, LV_PART_MAIN); + + lv_scale_set_range(scale, 0, 60); + + lv_scale_set_angle_range(scale, 360); + lv_scale_set_rotation(scale, 270); + + minute_hand = lv_line_create(scale); + lv_line_set_points_mutable(minute_hand, minute_hand_points, 2); + + lv_obj_set_style_line_width(minute_hand, 3, 0); + lv_obj_set_style_line_rounded(minute_hand, true, 0); + lv_obj_set_style_line_color(minute_hand, lv_color_white(), 0); + + hour_hand = lv_line_create(scale); + + lv_obj_set_style_line_width(hour_hand, 5, 0); + lv_obj_set_style_line_rounded(hour_hand, true, 0); + lv_obj_set_style_line_color(hour_hand, lv_palette_main(LV_PALETTE_RED), 0); + + hour = 11; + minute = 5; + lv_timer_t * timer = lv_timer_create(timer_cb, 250, NULL); + lv_timer_ready(timer); +} + +#endif diff --git a/src/widgets/line/lv_line.c b/src/widgets/line/lv_line.c index 9d5ee810d..fd384722f 100644 --- a/src/widgets/line/lv_line.c +++ b/src/widgets/line/lv_line.c @@ -27,6 +27,7 @@ * STATIC PROTOTYPES **********************/ static void lv_line_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void line_set_points(lv_obj_t * obj, const lv_point_precise_t points[], uint32_t point_num, bool mut); static void lv_line_event(const lv_obj_class_t * class_p, lv_event_t * e); /********************** @@ -64,15 +65,12 @@ lv_obj_t * lv_line_create(lv_obj_t * parent) void lv_line_set_points(lv_obj_t * obj, const lv_point_precise_t points[], uint32_t point_num) { - LV_ASSERT_OBJ(obj, MY_CLASS); + line_set_points(obj, points, point_num, false); +} - lv_line_t * line = (lv_line_t *)obj; - line->point_array = points; - line->point_num = point_num; - - lv_obj_refresh_self_size(obj); - - lv_obj_invalidate(obj); +void lv_line_set_points_mutable(lv_obj_t * obj, lv_point_precise_t points[], uint32_t point_num) +{ + line_set_points(obj, points, point_num, true); } void lv_line_set_y_invert(lv_obj_t * obj, bool en) @@ -91,6 +89,42 @@ void lv_line_set_y_invert(lv_obj_t * obj, bool en) * Getter functions *====================*/ +const lv_point_precise_t * lv_line_get_points(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_line_t * line = (lv_line_t *)obj; + return line->point_array.constant; +} + +uint32_t lv_line_get_point_count(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_line_t * line = (lv_line_t *)obj; + return line->point_num; +} + +bool lv_line_is_point_array_mutable(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_line_t * line = (lv_line_t *)obj; + return line->point_array_is_mutable; +} + +lv_point_precise_t * lv_line_get_points_mutable(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_line_t * line = (lv_line_t *)obj; + if(!line->point_array_is_mutable) { + LV_LOG_WARN("the line point array is not mutable"); + return NULL; + } + return line->point_array.mut; +} + bool lv_line_get_y_invert(const lv_obj_t * obj) { LV_ASSERT_OBJ(obj, MY_CLASS); @@ -112,14 +146,29 @@ static void lv_line_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) lv_line_t * line = (lv_line_t *)obj; line->point_num = 0; - line->point_array = NULL; + line->point_array.constant = NULL; line->y_inv = 0; + line->point_array_is_mutable = 0; lv_obj_remove_flag(obj, LV_OBJ_FLAG_CLICKABLE); LV_TRACE_OBJ_CREATE("finished"); } +static void line_set_points(lv_obj_t * obj, const lv_point_precise_t points[], uint32_t point_num, bool mut) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_line_t * line = (lv_line_t *)obj; + line->point_array.constant = points; + line->point_num = point_num; + line->point_array_is_mutable = mut; + + lv_obj_refresh_self_size(obj); + + lv_obj_invalidate(obj); +} + static inline lv_value_precise_t resolve_point_coord(lv_value_precise_t coord, int32_t max) { if(LV_COORD_IS_PCT((int32_t)coord)) { @@ -152,7 +201,7 @@ static void lv_line_event(const lv_obj_class_t * class_p, lv_event_t * e) else if(code == LV_EVENT_GET_SELF_SIZE) { lv_line_t * line = (lv_line_t *)obj; - if(line->point_num == 0 || line->point_array == NULL) return; + if(line->point_num == 0 || line->point_array.constant == NULL) return; lv_point_t * p = lv_event_get_param(e); int32_t w = 0; @@ -160,12 +209,12 @@ static void lv_line_event(const lv_obj_class_t * class_p, lv_event_t * e) uint32_t i; for(i = 0; i < line->point_num; i++) { - if(!LV_COORD_IS_PCT((int32_t)line->point_array[i].x)) { - w = (int32_t)LV_MAX(line->point_array[i].x, w); + if(!LV_COORD_IS_PCT((int32_t)line->point_array.constant[i].x)) { + w = (int32_t)LV_MAX(line->point_array.constant[i].x, w); } - if(!LV_COORD_IS_PCT((int32_t)line->point_array[i].y)) { - h = (int32_t)LV_MAX(line->point_array[i].y, h); + if(!LV_COORD_IS_PCT((int32_t)line->point_array.constant[i].y)) { + h = (int32_t)LV_MAX(line->point_array.constant[i].y, h); } } @@ -176,7 +225,7 @@ static void lv_line_event(const lv_obj_class_t * class_p, lv_event_t * e) lv_line_t * line = (lv_line_t *)obj; lv_layer_t * layer = lv_event_get_layer(e); - if(line->point_num == 0 || line->point_array == NULL) return; + if(line->point_num == 0 || line->point_array.constant == NULL) return; lv_area_t area; lv_obj_get_coords(obj, &area); @@ -193,11 +242,11 @@ static void lv_line_event(const lv_obj_class_t * class_p, lv_event_t * e) int32_t w = lv_obj_get_width(obj); int32_t h = lv_obj_get_height(obj); - line_dsc.p1.x = resolve_point_coord(line->point_array[i].x, w) + x_ofs; - line_dsc.p1.y = resolve_point_coord(line->point_array[i].y, h); + line_dsc.p1.x = resolve_point_coord(line->point_array.constant[i].x, w) + x_ofs; + line_dsc.p1.y = resolve_point_coord(line->point_array.constant[i].y, h); - line_dsc.p2.x = resolve_point_coord(line->point_array[i + 1].x, w) + x_ofs; - line_dsc.p2.y = resolve_point_coord(line->point_array[i + 1].y, h); + line_dsc.p2.x = resolve_point_coord(line->point_array.constant[i + 1].x, w) + x_ofs; + line_dsc.p2.y = resolve_point_coord(line->point_array.constant[i + 1].y, h); if(line->y_inv == 0) { line_dsc.p1.y = line_dsc.p1.y + y_ofs; diff --git a/src/widgets/line/lv_line.h b/src/widgets/line/lv_line.h index 6a2423a22..eac2482ed 100644 --- a/src/widgets/line/lv_line.h +++ b/src/widgets/line/lv_line.h @@ -27,9 +27,13 @@ extern "C" { /*Data of line*/ typedef struct { lv_obj_t obj; - const lv_point_precise_t * point_array; /**< Pointer to an array with the points of the line*/ + union { + const lv_point_precise_t * constant; + lv_point_precise_t * mut; + } point_array; /**< Pointer to an array with the points of the line*/ uint32_t point_num; /**< Number of points in 'point_array'*/ uint32_t y_inv : 1; /**< 1: y == 0 will be on the bottom*/ + uint32_t point_array_is_mutable : 1; /**< whether the point array is const or mutable*/ } lv_line_t; LV_ATTRIBUTE_EXTERN_DATA extern const lv_obj_class_t lv_line_class; @@ -57,6 +61,14 @@ lv_obj_t * lv_line_create(lv_obj_t * parent); */ void lv_line_set_points(lv_obj_t * obj, const lv_point_precise_t points[], uint32_t point_num); +/** + * Set a non-const array of points. Identical to `lv_line_set_points` except the array may be retrieved by `lv_line_get_points_mutable`. + * @param obj pointer to a line object + * @param points a non-const array of points. Only the address is saved, so the array needs to be alive while the line exists. + * @param point_num number of points in 'point_a' + */ +void lv_line_set_points_mutable(lv_obj_t * obj, lv_point_precise_t points[], uint32_t point_num); + /** * Enable (or disable) the y coordinate inversion. * If enabled then y will be subtracted from the height of the object, @@ -70,6 +82,34 @@ void lv_line_set_y_invert(lv_obj_t * obj, bool en); * Getter functions *====================*/ +/** + * Get the pointer to the array of points. + * @param obj pointer to a line object + * @return const pointer to the array of points + */ +const lv_point_precise_t * lv_line_get_points(lv_obj_t * obj); + +/** + * Get the number of points in the array of points. + * @param obj pointer to a line object + * @return number of points in array of points + */ +uint32_t lv_line_get_point_count(lv_obj_t * obj); + +/** + * Check the mutability of the stored point array pointer. + * @param obj pointer to a line object + * @return true: the point array pointer is mutable, false: constant + */ +bool lv_line_is_point_array_mutable(lv_obj_t * obj); + +/** + * Get a pointer to the mutable array of points or NULL if it is not mutable + * @param obj pointer to a line object + * @return pointer to the array of points. NULL if not mutable. + */ +lv_point_precise_t * lv_line_get_points_mutable(lv_obj_t * obj); + /** * Get the y inversion attribute * @param obj pointer to a line object diff --git a/src/widgets/scale/lv_scale.c b/src/widgets/scale/lv_scale.c index 1e8df7ade..68de22b6d 100644 --- a/src/widgets/scale/lv_scale.c +++ b/src/widgets/scale/lv_scale.c @@ -60,6 +60,8 @@ static void scale_store_section_line_tick_width_compensation(lv_obj_t * obj, con static void scale_build_custom_label_text(lv_obj_t * obj, lv_draw_label_dsc_t * label_dsc, const uint16_t major_tick_idx); +static void scale_free_line_needle_points_cb(lv_event_t * e); + /********************** * STATIC VARIABLES **********************/ @@ -180,7 +182,7 @@ void lv_scale_set_line_needle_value(lv_obj_t * obj, lv_obj_t * needle_line, int3 int32_t scale_width, scale_height; int32_t actual_needle_length; int32_t needle_length_x, needle_length_y; - static lv_point_precise_t needle_line_points[2]; + lv_point_precise_t * needle_line_points = NULL; LV_ASSERT_OBJ(obj, MY_CLASS); lv_scale_t * scale = (lv_scale_t *)obj; @@ -224,12 +226,35 @@ void lv_scale_set_line_needle_value(lv_obj_t * obj, lv_obj_t * needle_line, int3 needle_length_x = (actual_needle_length * lv_trigo_cos(scale->rotation + angle)) >> LV_TRIGO_SHIFT; needle_length_y = (actual_needle_length * lv_trigo_sin(scale->rotation + angle)) >> LV_TRIGO_SHIFT; + if(lv_line_is_point_array_mutable(needle_line) && lv_line_get_point_count(needle_line) >= 2) { + needle_line_points = lv_line_get_points_mutable(needle_line); + } + + if(needle_line_points == NULL) { + uint32_t i; + uint32_t line_event_cnt = lv_obj_get_event_count(needle_line); + for(i = 0; i < line_event_cnt; i--) { + lv_event_dsc_t * dsc = lv_obj_get_event_dsc(needle_line, i); + if(lv_event_dsc_get_cb(dsc) == scale_free_line_needle_points_cb) { + needle_line_points = lv_event_dsc_get_user_data(dsc); + break; + } + } + } + + if(needle_line_points == NULL) { + needle_line_points = lv_malloc(sizeof(lv_point_precise_t) * 2); + LV_ASSERT_MALLOC(needle_line_points); + if(needle_line_points == NULL) return; + lv_obj_add_event_cb(needle_line, scale_free_line_needle_points_cb, LV_EVENT_DELETE, needle_line_points); + } + needle_line_points[0].x = scale_width / 2; needle_line_points[0].y = scale_height / 2; needle_line_points[1].x = scale_width / 2 + needle_length_x; needle_line_points[1].y = scale_height / 2 + needle_length_y; - lv_line_set_points(needle_line, needle_line_points, 2); + lv_line_set_points_mutable(needle_line, needle_line_points, 2); } void lv_scale_set_image_needle_value(lv_obj_t * obj, lv_obj_t * needle_img, int32_t value) @@ -1441,4 +1466,10 @@ static void scale_store_section_line_tick_width_compensation(lv_obj_t * obj, con } } +static void scale_free_line_needle_points_cb(lv_event_t * e) +{ + lv_point_precise_t * needle_line_points = lv_event_get_user_data(e); + lv_free(needle_line_points); +} + #endif diff --git a/src/widgets/scale/lv_scale.h b/src/widgets/scale/lv_scale.h index 8ac377eb1..a71478f8f 100644 --- a/src/widgets/scale/lv_scale.h +++ b/src/widgets/scale/lv_scale.h @@ -165,7 +165,9 @@ void lv_scale_set_rotation(lv_obj_t * obj, int32_t rotation); /** * Point the needle to the corresponding value through the line * @param obj pointer to a scale object - * @param needle_line needle_line of the scale + * @param needle_line needle_line of the scale. The line points will be allocated and + * managed by the scale unless the line point array was previously set + * using `lv_line_set_points_mutable`. * @param needle_length length of the needle * needle_length>0 needle_length=needle_length; * needle_length<0 needle_length=radius-|needle_length|; diff --git a/tests/src/test_cases/widgets/test_line.c b/tests/src/test_cases/widgets/test_line.c index 8daa6ea02..beb2d3af6 100644 --- a/tests/src/test_cases/widgets/test_line.c +++ b/tests/src/test_cases/widgets/test_line.c @@ -23,9 +23,8 @@ void tearDown(void) void test_line_should_have_valid_documented_default_values(void) { - lv_line_t * line_ptr = (lv_line_t *) line; - TEST_ASSERT_EQUAL_UINT16(default_point_num, line_ptr->point_num); - TEST_ASSERT_NULL(line_ptr->point_array); + TEST_ASSERT_EQUAL_UINT16(default_point_num, lv_line_get_point_count(line)); + TEST_ASSERT_NULL(lv_line_get_points(line)); TEST_ASSERT_FALSE(lv_line_get_y_invert(line)); TEST_ASSERT_FALSE(lv_obj_has_flag(line, LV_OBJ_FLAG_CLICKABLE)); /* line doesn't have any points, so it's 0,0 in size */ @@ -145,4 +144,21 @@ void test_line_dash_gap(void) TEST_ASSERT_EQUAL_SCREENSHOT("widgets/line_2.png"); } +void test_line_point_array_getters_and_setters(void) +{ + const lv_point_precise_t points[3] = {{10, 20}, {30, 40}, {50, 60}}; + lv_line_set_points(line, points, 3); + TEST_ASSERT_EQUAL_UINT32(3, lv_line_get_point_count(line)); + TEST_ASSERT_FALSE(lv_line_is_point_array_mutable(line)); + TEST_ASSERT_EQUAL_PTR(points, lv_line_get_points(line)); + TEST_ASSERT_NULL(lv_line_get_points_mutable(line)); + + lv_point_precise_t points_mutable[2] = {{10, 20}, {30, 40}}; + lv_line_set_points_mutable(line, points_mutable, 2); + TEST_ASSERT_EQUAL_UINT32(2, lv_line_get_point_count(line)); + TEST_ASSERT_TRUE(lv_line_is_point_array_mutable(line)); + TEST_ASSERT_EQUAL_PTR(points_mutable, lv_line_get_points(line)); + TEST_ASSERT_EQUAL_PTR(points_mutable, lv_line_get_points_mutable(line)); +} + #endif diff --git a/tests/src/test_cases/widgets/test_scale.c b/tests/src/test_cases/widgets/test_scale.c index b08b09fe9..00da0441f 100644 --- a/tests/src/test_cases/widgets/test_scale.c +++ b/tests/src/test_cases/widgets/test_scale.c @@ -364,4 +364,43 @@ void test_scale_range(void) TEST_ASSERT_EQUAL(max_range, lv_scale_get_range_max_value(scale)); } +void test_scale_set_line_needle_value(void) +{ + lv_obj_t * scale = lv_scale_create(lv_screen_active()); + lv_scale_set_mode(scale, LV_SCALE_MODE_ROUND_INNER); + + lv_obj_t * line = lv_line_create(scale); + + /* test the scale alocating the array */ + lv_scale_set_line_needle_value(scale, line, 50, 35); + TEST_ASSERT_EQUAL_UINT32(2, lv_line_get_point_count(line)); + const lv_point_precise_t * allocated_points_array = lv_line_get_points(line); + TEST_ASSERT_NOT_NULL(allocated_points_array); + TEST_ASSERT_TRUE(lv_line_is_point_array_mutable(line)); + TEST_ASSERT_EQUAL_PTR(allocated_points_array, lv_line_get_points_mutable(line)); + + /* test the scale using the line's pre-set mutable array */ + lv_point_precise_t provided_points_array[2] = {{-100, -100}, {-100, -100}}; + lv_line_set_points_mutable(line, provided_points_array, 2); + lv_scale_set_line_needle_value(scale, line, 20, 20); + TEST_ASSERT( + provided_points_array[0].x != -100 || provided_points_array[0].y != -100 + || provided_points_array[1].x != -100 || provided_points_array[1].y != -100 + ); + TEST_ASSERT_EQUAL_PTR(provided_points_array, lv_line_get_points_mutable(line)); + + provided_points_array[0].x = -100; + provided_points_array[0].y = -100; + provided_points_array[1].x = -100; + provided_points_array[1].y = -100; + /* set the line array to an immutable one. The scale will switch back to its allocated one */ + lv_line_set_points(line, provided_points_array, 2); /* immutable setter */ + lv_scale_set_line_needle_value(scale, line, 10, 30); + TEST_ASSERT_EQUAL_PTR(allocated_points_array, lv_line_get_points_mutable(line)); + TEST_ASSERT( + provided_points_array[0].x == -100 && provided_points_array[0].y == -100 + && provided_points_array[1].x == -100 && provided_points_array[1].y == -100 + ); +} + #endif