diff --git a/src/core/lv_obj.c b/src/core/lv_obj.c index 3ba67b92f3..115c7f6844 100644 --- a/src/core/lv_obj.c +++ b/src/core/lv_obj.c @@ -1035,11 +1035,9 @@ static void lv_obj_event(const lv_obj_class_t * class_p, lv_event_t * e) } } else if(code == LV_EVENT_CHILD_CHANGED) { - int32_t w = lv_obj_get_style_width(obj, LV_PART_MAIN); - int32_t h = lv_obj_get_style_height(obj, LV_PART_MAIN); int32_t align = lv_obj_get_style_align(obj, LV_PART_MAIN); uint16_t layout = lv_obj_get_style_layout(obj, LV_PART_MAIN); - if(layout || align || w == LV_SIZE_CONTENT || h == LV_SIZE_CONTENT) { + if(layout || align || lv_obj_is_style_any_width_content(obj) || lv_obj_is_style_any_height_content(obj)) { lv_obj_mark_layout_as_dirty(obj); } } diff --git a/src/core/lv_obj_pos.c b/src/core/lv_obj_pos.c index 177b0fb5ba..4f3847e45d 100644 --- a/src/core/lv_obj_pos.c +++ b/src/core/lv_obj_pos.c @@ -728,6 +728,26 @@ int32_t lv_obj_get_style_clamped_height(lv_obj_t * obj) return h; } +bool lv_obj_is_style_any_width_content(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + int32_t w = lv_obj_get_style_width(obj, LV_PART_MAIN); + int32_t minw = lv_obj_get_style_min_width(obj, LV_PART_MAIN); + int32_t maxw = lv_obj_get_style_max_width(obj, LV_PART_MAIN); + return (w == LV_SIZE_CONTENT || minw == LV_SIZE_CONTENT || maxw == LV_SIZE_CONTENT); +} + +bool lv_obj_is_style_any_height_content(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + int32_t h = lv_obj_get_style_height(obj, LV_PART_MAIN); + int32_t minh = lv_obj_get_style_min_height(obj, LV_PART_MAIN); + int32_t maxh = lv_obj_get_style_max_height(obj, LV_PART_MAIN); + return (h == LV_SIZE_CONTENT || minh == LV_SIZE_CONTENT || maxh == LV_SIZE_CONTENT); +} + bool lv_obj_is_width_min(lv_obj_t * obj) { LV_ASSERT_OBJ(obj, MY_CLASS); @@ -766,10 +786,19 @@ bool lv_obj_is_height_max(lv_obj_t * obj) bool lv_obj_refresh_self_size(lv_obj_t * obj) { - int32_t w_set = lv_obj_get_style_width(obj, LV_PART_MAIN); - int32_t h_set = lv_obj_get_style_height(obj, LV_PART_MAIN); - if(w_set != LV_SIZE_CONTENT && h_set != LV_SIZE_CONTENT) return false; + if(!lv_obj_is_style_any_width_content(obj) && !lv_obj_is_style_any_height_content(obj)) + return false; + /** + * Refresh the parent's layout, because the childs size is in some way dependent on its contents we need to force a + * recalculation of the parents layout + */ + lv_obj_t * parent = lv_obj_get_parent(obj); + if(parent != NULL) { + parent->w_layout = 0; + parent->h_layout = 0; + lv_obj_mark_layout_as_dirty(parent); + } lv_obj_mark_layout_as_dirty(obj); return true; } diff --git a/src/core/lv_obj_pos.h b/src/core/lv_obj_pos.h index c3913e10c4..9b9f3fe92e 100644 --- a/src/core/lv_obj_pos.h +++ b/src/core/lv_obj_pos.h @@ -362,6 +362,22 @@ int32_t lv_obj_get_style_clamped_width(lv_obj_t * obj); */ int32_t lv_obj_get_style_clamped_height(lv_obj_t * obj); +/** + * Determine if any of the object's width style properties are set to `LV_SIZE_CONTENT`. + * @param obj Pointer to a valid object. + * @return `true` At least one of the following width style properties is `LV_SIZE_CONTENT`: `LV_STYLE_WIDTH`, `LV_STYLE_MIN_WIDTH`, `LV_STYLE_MAX_WIDTH`. + * @return `false` No width style properties are `LV_SIZE_CONTENT`. + */ +bool lv_obj_is_style_any_width_content(lv_obj_t * obj); + +/** + * Determine if any of the object's height style properties are set to `LV_SIZE_CONTENT`. + * @param obj Pointer to a valid object. + * @return `true` At least one of the following height style properties is `LV_SIZE_CONTENT`: `LV_STYLE_HEIGHT`, `LV_STYLE_MIN_HEIGHT`, `LV_STYLE_MAX_HEIGHT`. + * @return `false` No height style properties are `LV_SIZE_CONTENT`. + */ +bool lv_obj_is_style_any_height_content(lv_obj_t * obj); + /** * @brief Determine if the object's resolved width was limited by its minimum width constraint. * diff --git a/src/widgets/dropdown/lv_dropdown.c b/src/widgets/dropdown/lv_dropdown.c index 2c7ddb09b5..d08e1f46d9 100644 --- a/src/widgets/dropdown/lv_dropdown.c +++ b/src/widgets/dropdown/lv_dropdown.c @@ -45,6 +45,7 @@ static void lv_dropdown_constructor(const lv_obj_class_t * class_p, lv_obj_t * o static void lv_dropdown_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); static void lv_dropdown_event(const lv_obj_class_t * class_p, lv_event_t * e); static void draw_main(lv_event_t * e); +static void refresh_size(lv_obj_t * obj); static void lv_dropdownlist_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); static void lv_dropdownlist_destructor(const lv_obj_class_t * class_p, lv_obj_t * list_obj); @@ -165,6 +166,9 @@ void lv_dropdown_set_text(lv_obj_t * obj, const char * text) { LV_ASSERT_OBJ(obj, MY_CLASS); lv_dropdown_t * dropdown = (lv_dropdown_t *)obj; + if(!dropdown->static_text && dropdown->text && text && lv_strcmp(dropdown->text, text) == 0) { + return; + } char * copied_text = NULL; if(text) { @@ -184,11 +188,16 @@ void lv_dropdown_set_text_static(lv_obj_t * obj, const char * text) LV_ASSERT_OBJ(obj, MY_CLASS); lv_dropdown_t * dropdown = (lv_dropdown_t *)obj; - if(!dropdown->static_text) lv_free(dropdown->text); + if(dropdown->static_text && dropdown->text && text && lv_strcmp(dropdown->text, text) == 0) { + return; + } + + if(!dropdown->static_text) + lv_free(dropdown->text); dropdown->static_text = 1; dropdown->text = (char *)text; - lv_obj_invalidate(obj); + refresh_size(obj); } void lv_dropdown_set_options(lv_obj_t * obj, const char * options) @@ -234,8 +243,9 @@ void lv_dropdown_set_options(lv_obj_t * obj, const char * options) /*Now the text is dynamically allocated*/ dropdown->static_options = 0; - lv_obj_invalidate(obj); - if(dropdown->list) lv_obj_invalidate(dropdown->list); + refresh_size(obj); + if(dropdown->list) + lv_obj_invalidate(dropdown->list); } void lv_dropdown_set_options_static(lv_obj_t * obj, const char * options) @@ -263,8 +273,9 @@ void lv_dropdown_set_options_static(lv_obj_t * obj, const char * options) dropdown->static_options = 1; dropdown->options = (char *)options; - lv_obj_invalidate(obj); - if(dropdown->list) lv_obj_invalidate(dropdown->list); + refresh_size(obj); + if(dropdown->list) + lv_obj_invalidate(dropdown->list); } void lv_dropdown_add_option(lv_obj_t * obj, const char * option, uint32_t pos) @@ -335,8 +346,9 @@ void lv_dropdown_add_option(lv_obj_t * obj, const char * option, uint32_t pos) dropdown->option_cnt++; - lv_obj_invalidate(obj); - if(dropdown->list) lv_obj_invalidate(dropdown->list); + refresh_size(obj); + if(dropdown->list) + lv_obj_invalidate(dropdown->list); } void lv_dropdown_clear_options(lv_obj_t * obj) @@ -352,8 +364,9 @@ void lv_dropdown_clear_options(lv_obj_t * obj) dropdown->static_options = 1; dropdown->option_cnt = 0; - lv_obj_invalidate(obj); - if(dropdown->list) lv_obj_invalidate(dropdown->list); + refresh_size(obj); + if(dropdown->list) + lv_obj_invalidate(dropdown->list); } void lv_dropdown_set_selected(lv_obj_t * obj, uint32_t sel_opt) @@ -370,7 +383,7 @@ void lv_dropdown_set_selected(lv_obj_t * obj, uint32_t sel_opt) position_to_selected(obj, LV_ANIM_OFF); } - lv_obj_invalidate(obj); + refresh_size(obj); } void lv_dropdown_set_dir(lv_obj_t * obj, lv_dir_t dir) @@ -391,7 +404,7 @@ void lv_dropdown_set_symbol(lv_obj_t * obj, const void * symbol) lv_dropdown_t * dropdown = (lv_dropdown_t *)obj; dropdown->symbol = symbol; - lv_obj_invalidate(obj); + refresh_size(obj); } void lv_dropdown_set_selected_highlight(lv_obj_t * obj, bool en) @@ -795,7 +808,11 @@ static void lv_dropdown_event(const lv_obj_class_t * class_p, lv_event_t * e) } else if(code == LV_EVENT_RELEASED) { res = btn_release_handler(obj); - if(res != LV_RESULT_OK) return; + if(res != LV_RESULT_OK) + return; + } + else if(code == LV_EVENT_VALUE_CHANGED) { + refresh_size(obj); } else if(code == LV_EVENT_STYLE_CHANGED) { lv_obj_refresh_self_size(obj); @@ -804,9 +821,73 @@ static void lv_dropdown_event(const lv_obj_class_t * class_p, lv_event_t * e) lv_obj_refresh_self_size(obj); } else if(code == LV_EVENT_GET_SELF_SIZE) { - lv_point_t * p = lv_event_get_param(e); const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN); - p->y = lv_font_get_line_height(font); + + lv_point_t size; + size.x = 0; + size.y = 0; + + /* Calculate the symbol width */ + int32_t symbol_w = 0; + if(dropdown->symbol) { + lv_draw_label_dsc_t symbol_dsc; + lv_draw_label_dsc_init(&symbol_dsc); + + lv_image_src_t symbol_type = lv_image_src_get_type(dropdown->symbol); + if(symbol_type == LV_IMAGE_SRC_SYMBOL) { + lv_point_t text_size; + + lv_text_get_size(&text_size, + dropdown->symbol, + symbol_dsc.font, + symbol_dsc.letter_space, + symbol_dsc.line_space, + LV_COORD_MAX, + symbol_dsc.flag); + symbol_w = text_size.x; + } + else { + lv_image_header_t header; + lv_result_t decoder_res = lv_image_decoder_get_info(dropdown->symbol, &header); + if(decoder_res == LV_RESULT_OK) { + symbol_w = header.w; + } + else { + symbol_w = 0; + } + } + size.x += symbol_w; + size.x += lv_obj_get_style_pad_column(obj, LV_PART_MAIN); + } + + /* Calculate the text width */ + const char * opt_txt; + char buf[128]; + if(dropdown->text) + opt_txt = dropdown->text; + else { + lv_dropdown_get_selected_str(obj, buf, 128); + opt_txt = buf; + } + + if(opt_txt == NULL) { + size.y = LV_MAX(size.y, lv_font_get_line_height(font)); + } + else { + lv_draw_label_dsc_t dsc; + lv_draw_label_dsc_init(&dsc); + + lv_point_t text_size; + int32_t max_width = lv_obj_calc_dynamic_width(obj, LV_STYLE_MAX_WIDTH) - size.x; + lv_text_get_size(&text_size, opt_txt, font, dsc.letter_space, dsc.line_space, max_width, dsc.flag); + + size.x += text_size.x; + size.y = LV_MAX(size.y, text_size.y); + } + + lv_point_t * p = lv_event_get_param(e); + p->x = LV_MAX(p->x, size.x); + p->y = LV_MAX(p->y, size.y); } else if(code == LV_EVENT_KEY) { uint32_t c = lv_event_get_key(e); @@ -839,7 +920,8 @@ static void lv_dropdown_event(const lv_obj_class_t * class_p, lv_event_t * e) lv_obj_t * indev_obj = lv_indev_get_active_obj(); if(indev_obj != obj) { res = btn_release_handler(obj); - if(res != LV_RESULT_OK) return; + if(res != LV_RESULT_OK) + return; } } } @@ -1032,6 +1114,12 @@ static void draw_main(lv_event_t * e) lv_draw_label(layer, &label_dsc, &txt_area); } +static void refresh_size(lv_obj_t * obj) +{ + lv_obj_invalidate(obj); + lv_obj_refresh_self_size(obj); +} + static void draw_list(lv_event_t * e) { lv_obj_t * list_obj = lv_event_get_current_target(e); diff --git a/src/widgets/label/lv_label.c b/src/widgets/label/lv_label.c index d550043c5e..0a25fc86ad 100644 --- a/src/widgets/label/lv_label.c +++ b/src/widgets/label/lv_label.c @@ -1063,7 +1063,16 @@ static void lv_label_mark_need_refr_text(lv_obj_t * obj) label->invalid_size_cache = true; lv_obj_invalidate(obj); - lv_obj_refresh_self_size(obj); + + /** + * Ideally we would use `lv_obj_refresh_self_size(obj);` here but it can cause an infinite loop due to the way label + * self size is implemented. + * The implementation should be revisited in the future since it currently doesn't handle fixed height, content + * width in all scenarios properly. + * Once that is fixed we should be able to use `lv_obj_refresh_self_size(obj);` here. + */ + if(lv_obj_is_style_any_height_content(obj) || lv_obj_is_style_any_width_content(obj)) + lv_obj_mark_layout_as_dirty(obj); if(!label->need_refr_text) { label->need_refr_text = true; diff --git a/tests/ref_imgs/widgets/dropdown_content_size_1.png b/tests/ref_imgs/widgets/dropdown_content_size_1.png new file mode 100644 index 0000000000..a0835d4046 Binary files /dev/null and b/tests/ref_imgs/widgets/dropdown_content_size_1.png differ diff --git a/tests/ref_imgs/widgets/dropdown_content_size_1_open.png b/tests/ref_imgs/widgets/dropdown_content_size_1_open.png new file mode 100644 index 0000000000..f5b040e08f Binary files /dev/null and b/tests/ref_imgs/widgets/dropdown_content_size_1_open.png differ diff --git a/tests/ref_imgs/widgets/dropdown_content_size_2.png b/tests/ref_imgs/widgets/dropdown_content_size_2.png new file mode 100644 index 0000000000..c41e151cc5 Binary files /dev/null and b/tests/ref_imgs/widgets/dropdown_content_size_2.png differ diff --git a/tests/ref_imgs/widgets/dropdown_content_size_3.png b/tests/ref_imgs/widgets/dropdown_content_size_3.png new file mode 100644 index 0000000000..3860154f5a Binary files /dev/null and b/tests/ref_imgs/widgets/dropdown_content_size_3.png differ diff --git a/tests/ref_imgs/widgets/obj_pos_chained_layout_invalidation_post.png b/tests/ref_imgs/widgets/obj_pos_chained_layout_invalidation_post.png new file mode 100644 index 0000000000..3dd98e99e9 Binary files /dev/null and b/tests/ref_imgs/widgets/obj_pos_chained_layout_invalidation_post.png differ diff --git a/tests/ref_imgs/widgets/obj_pos_chained_layout_invalidation_pre.png b/tests/ref_imgs/widgets/obj_pos_chained_layout_invalidation_pre.png new file mode 100644 index 0000000000..0b6ca8ca9a Binary files /dev/null and b/tests/ref_imgs/widgets/obj_pos_chained_layout_invalidation_pre.png differ diff --git a/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_1.png b/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_1.png new file mode 100644 index 0000000000..9ac062205b Binary files /dev/null and b/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_1.png differ diff --git a/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_1_open.png b/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_1_open.png new file mode 100644 index 0000000000..b408cd4a76 Binary files /dev/null and b/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_1_open.png differ diff --git a/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_2.png b/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_2.png new file mode 100644 index 0000000000..6b83cb5c94 Binary files /dev/null and b/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_2.png differ diff --git a/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_3.png b/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_3.png new file mode 100644 index 0000000000..44e4e29181 Binary files /dev/null and b/tests/ref_imgs_vg_lite/widgets/dropdown_content_size_3.png differ diff --git a/tests/ref_imgs_vg_lite/widgets/obj_pos_chained_layout_invalidation_post.png b/tests/ref_imgs_vg_lite/widgets/obj_pos_chained_layout_invalidation_post.png new file mode 100644 index 0000000000..0a56e7ba72 Binary files /dev/null and b/tests/ref_imgs_vg_lite/widgets/obj_pos_chained_layout_invalidation_post.png differ diff --git a/tests/ref_imgs_vg_lite/widgets/obj_pos_chained_layout_invalidation_pre.png b/tests/ref_imgs_vg_lite/widgets/obj_pos_chained_layout_invalidation_pre.png new file mode 100644 index 0000000000..be027c8d60 Binary files /dev/null and b/tests/ref_imgs_vg_lite/widgets/obj_pos_chained_layout_invalidation_pre.png differ diff --git a/tests/src/test_cases/widgets/test_dropdown.c b/tests/src/test_cases/widgets/test_dropdown.c index 8c3d64469d..465f60cfe8 100644 --- a/tests/src/test_cases/widgets/test_dropdown.c +++ b/tests/src/test_cases/widgets/test_dropdown.c @@ -540,5 +540,22 @@ void test_dropdown_properties(void) #endif } +void test_dropdown_content_size() +{ + lv_obj_t * dd = lv_dropdown_create(lv_screen_active()); + lv_dropdown_set_options(dd, "Short\nA bit longer option\nThe longest option in the list"); + lv_obj_set_width(dd, LV_SIZE_CONTENT); + TEST_ASSERT_EQUAL_SCREENSHOT("widgets/dropdown_content_size_1.png"); + lv_dropdown_open(dd); + TEST_ASSERT_EQUAL_SCREENSHOT("widgets/dropdown_content_size_1_open.png"); + + lv_dropdown_set_selected(dd, 1); + lv_obj_refresh_self_size(lv_screen_active()); + lv_obj_invalidate(lv_screen_active()); + TEST_ASSERT_EQUAL_SCREENSHOT("widgets/dropdown_content_size_2.png"); + + lv_dropdown_set_selected(dd, 2); + TEST_ASSERT_EQUAL_SCREENSHOT("widgets/dropdown_content_size_3.png"); +} #endif diff --git a/tests/src/test_cases/widgets/test_obj_pos.c b/tests/src/test_cases/widgets/test_obj_pos.c index 75dd3ef6ff..3d0869d4a6 100644 --- a/tests/src/test_cases/widgets/test_obj_pos.c +++ b/tests/src/test_cases/widgets/test_obj_pos.c @@ -81,17 +81,40 @@ void test_style_min_size(void) lv_obj_set_style_min_width(child, LV_SIZE_CONTENT, 0); lv_obj_set_style_min_height(child, LV_SIZE_CONTENT, 0); TEST_ASSERT_EQUAL_SCREENSHOT("widgets/obj_pos_content_min_size.png"); - TEST_ASSERT_TRUE(lv_obj_is_width_min(child)); - TEST_ASSERT_TRUE(lv_obj_is_height_min(child)); +} - lv_obj_set_size(parent, 100, 100); - lv_obj_set_style_min_width(child, 0, 0); - lv_obj_set_style_min_height(child, 0, 0); - lv_refr_now(NULL); - TEST_ASSERT_FALSE(lv_obj_is_width_min(child)); - TEST_ASSERT_FALSE(lv_obj_is_height_min(child)); - TEST_ASSERT_EQUAL(LV_PCT(100), lv_obj_get_style_clamped_width(child)); - TEST_ASSERT_EQUAL(LV_PCT(100), lv_obj_get_style_clamped_height(child)); +void test_chaining_invalidation_layout(void) +{ + lv_obj_t * cont = lv_obj_create(lv_screen_active()); + lv_obj_set_name(cont, "cont"); + lv_obj_set_size(cont, 500, LV_SIZE_CONTENT); + lv_obj_set_style_bg_color(cont, lv_palette_main(LV_PALETTE_RED), 0); + lv_obj_set_style_bg_opa(cont, LV_OPA_COVER, 0); + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW_WRAP); + + lv_obj_t * label = lv_label_create(cont); + lv_obj_set_name(label, "label"); + lv_label_set_text(label, "Dropdown with size content:"); + + lv_obj_t * sub_cont = lv_obj_create(cont); + lv_obj_set_name(sub_cont, "sub_cont"); + lv_obj_set_style_bg_color(sub_cont, lv_palette_main(LV_PALETTE_GREEN), 0); + lv_obj_set_style_bg_opa(sub_cont, LV_OPA_COVER, 0); + lv_obj_set_height(sub_cont, LV_SIZE_CONTENT); + lv_obj_set_flex_flow(sub_cont, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_grow(sub_cont, 1); + lv_obj_set_style_min_width(sub_cont, LV_SIZE_CONTENT, LV_PART_MAIN); + + lv_obj_t * dd = lv_dropdown_create(sub_cont); + lv_obj_set_name(dd, "dropdown"); + lv_dropdown_set_options(dd, "Short\nA bit longer option\nThe longest option in the list"); + lv_obj_set_width(dd, 0); + lv_obj_set_style_min_width(dd, LV_SIZE_CONTENT, 0); + lv_obj_set_flex_grow(dd, 1); + + TEST_ASSERT_EQUAL_SCREENSHOT("widgets/obj_pos_chained_layout_invalidation_pre.png"); + lv_dropdown_set_selected(dd, 2); + TEST_ASSERT_EQUAL_SCREENSHOT("widgets/obj_pos_chained_layout_invalidation_post.png"); } void test_circular_height_dependency(void)